edsl 0.1.46__py3-none-any.whl → 0.1.48__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +430 -113
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/dataset/dataset_operations_mixin.py +1492 -0
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +12 -12
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +321 -155
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +20 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +10 -16
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +420 -216
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/file_store.py +755 -0
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +424 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +20 -21
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/METADATA +3 -2
  230. edsl-0.1.48.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -426
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/auto/AutoStudy.py +0 -130
  236. edsl/auto/StageBase.py +0 -243
  237. edsl/auto/StageGenerateSurvey.py +0 -178
  238. edsl/auto/StageLabelQuestions.py +0 -125
  239. edsl/auto/StagePersona.py +0 -61
  240. edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
  241. edsl/auto/StagePersonaDimensionValues.py +0 -74
  242. edsl/auto/StagePersonaDimensions.py +0 -69
  243. edsl/auto/StageQuestions.py +0 -74
  244. edsl/auto/SurveyCreatorPipeline.py +0 -21
  245. edsl/auto/utilities.py +0 -218
  246. edsl/base/Base.py +0 -279
  247. edsl/coop/PriceFetcher.py +0 -54
  248. edsl/data/Cache.py +0 -580
  249. edsl/data/CacheEntry.py +0 -230
  250. edsl/data/SQLiteDict.py +0 -292
  251. edsl/data/__init__.py +0 -5
  252. edsl/data/orm.py +0 -10
  253. edsl/exceptions/cache.py +0 -5
  254. edsl/exceptions/coop.py +0 -14
  255. edsl/exceptions/data.py +0 -14
  256. edsl/exceptions/scenarios.py +0 -29
  257. edsl/jobs/Answers.py +0 -43
  258. edsl/jobs/JobsPrompts.py +0 -354
  259. edsl/jobs/buckets/BucketCollection.py +0 -134
  260. edsl/jobs/buckets/ModelBuckets.py +0 -65
  261. edsl/jobs/buckets/TokenBucket.py +0 -283
  262. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  263. edsl/jobs/interviews/Interview.py +0 -395
  264. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  265. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  266. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  267. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  268. edsl/jobs/tasks/TaskCreators.py +0 -64
  269. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  270. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  271. edsl/language_models/LanguageModel.py +0 -635
  272. edsl/language_models/ServiceDataSources.py +0 -0
  273. edsl/language_models/key_management/KeyLookup.py +0 -63
  274. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  275. edsl/language_models/key_management/models.py +0 -137
  276. edsl/questions/QuestionBase.py +0 -539
  277. edsl/questions/QuestionFreeText.py +0 -130
  278. edsl/questions/derived/QuestionLikertFive.py +0 -76
  279. edsl/results/DatasetExportMixin.py +0 -911
  280. edsl/results/ResultsExportMixin.py +0 -45
  281. edsl/results/TextEditor.py +0 -50
  282. edsl/results/results_fetch_mixin.py +0 -33
  283. edsl/results/results_tools_mixin.py +0 -98
  284. edsl/scenarios/DocumentChunker.py +0 -104
  285. edsl/scenarios/FileStore.py +0 -564
  286. edsl/scenarios/Scenario.py +0 -548
  287. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  288. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  289. edsl/scenarios/handlers/latex.py +0 -5
  290. edsl/shared.py +0 -1
  291. edsl/surveys/Survey.py +0 -1306
  292. edsl/surveys/SurveyQualtricsImport.py +0 -284
  293. edsl/surveys/SurveyToApp.py +0 -141
  294. edsl/surveys/instructions/__init__.py +0 -0
  295. edsl/tools/__init__.py +0 -1
  296. edsl/tools/clusters.py +0 -192
  297. edsl/tools/embeddings.py +0 -27
  298. edsl/tools/embeddings_plotting.py +0 -118
  299. edsl/tools/plotting.py +0 -112
  300. edsl/tools/summarize.py +0 -18
  301. edsl/utilities/data/Registry.py +0 -6
  302. edsl/utilities/data/__init__.py +0 -1
  303. edsl/utilities/data/scooter_results.json +0 -1
  304. edsl-0.1.46.dist-info/RECORD +0 -366
  305. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  306. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  307. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  308. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  309. /edsl/{results → dataset/display}/table_display.css +0 -0
  310. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  311. /edsl/{results → dataset}/tree_explore.py +0 -0
  312. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  313. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  314. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  315. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  316. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  317. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  318. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  319. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  320. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  321. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  322. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  323. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  324. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  325. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  326. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  327. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
  328. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -17,16 +17,55 @@ def get_input_with_timeout(prompt, timeout=5, default="y"):
17
17
 
18
18
 
19
19
  class ExpectedParrotKeyHandler:
20
+ """
21
+ Manages Expected Parrot API keys for user authentication.
22
+
23
+ This class handles the storage, retrieval, and management of Expected Parrot API keys.
24
+ It provides functionality to securely store API keys in platform-specific user
25
+ configuration directories and retrieve them when needed. It also handles key
26
+ preference management (e.g., environment variables vs. stored keys).
27
+
28
+ The key handler follows a priority order when retrieving keys:
29
+ 1. Environment variables (EXPECTED_PARROT_API_KEY)
30
+ 2. Platform-specific user config directory
31
+
32
+ Attributes:
33
+ asked_to_store_file_name (str): Filename for tracking if user was asked about storage
34
+ ep_key_file_name (str): Filename for the stored API key
35
+ application_name (str): Application name for the config directory
36
+ """
20
37
  asked_to_store_file_name = "asked_to_store.txt"
21
38
  ep_key_file_name = "ep_api_key.txt"
22
39
  application_name = "edsl"
23
40
 
24
41
  @property
25
- def config_dir(self):
42
+ def config_dir(self) -> str:
43
+ """
44
+ Get the platform-specific user configuration directory for the application.
45
+
46
+ This property uses the platformdirs library to determine the appropriate
47
+ location for storing configuration files based on the user's operating system.
48
+
49
+ Returns:
50
+ str: Path to the user configuration directory
51
+
52
+ Notes:
53
+ - On Windows, typically: C:\\Users\\<username>\\AppData\\Local\\edsl
54
+ - On macOS, typically: ~/Library/Application Support/edsl
55
+ - On Linux, typically: ~/.config/edsl
56
+ """
26
57
  return platformdirs.user_config_dir(self.application_name)
27
58
 
28
59
  def _ep_key_file_exists(self) -> bool:
29
- """Check if the Expected Parrot key file exists."""
60
+ """
61
+ Check if the Expected Parrot key file exists in the config directory.
62
+
63
+ Returns:
64
+ bool: True if the key file exists, False otherwise
65
+
66
+ Notes:
67
+ - Does not check the validity of the stored key
68
+ """
30
69
  return Path(self.config_dir).joinpath(self.ep_key_file_name).exists()
31
70
 
32
71
  def ok_to_ask_to_store(self):
@@ -73,37 +112,59 @@ class ExpectedParrotKeyHandler:
73
112
  f.write("Yes")
74
113
  return False
75
114
 
76
- def get_ep_api_key(self):
77
- # check if the key is stored in the config_dir
115
+ def get_ep_api_key(self) -> str:
116
+ """
117
+ Retrieve the Expected Parrot API key from available sources.
118
+
119
+ This method checks multiple sources for the API key, with the following priority:
120
+ 1. Environment variable (EXPECTED_PARROT_API_KEY)
121
+ 2. Stored key in the user's config directory
122
+
123
+ If keys are found in multiple sources and they differ, the environment
124
+ variable takes precedence. A warning is issued in this case.
125
+
126
+ Returns:
127
+ str: The Expected Parrot API key, or None if not found
128
+
129
+ Notes:
130
+ - If a key is found, it will attempt to store it persistently if appropriate
131
+ - Warnings are issued if conflicting keys are found in different sources
132
+ - Environment variables always take precedence over stored keys
133
+ """
134
+ # Initialize variables
78
135
  api_key = None
79
136
  api_key_from_cache = None
80
137
  api_key_from_os = None
81
138
 
139
+ # Try to get key from config directory
82
140
  if self._ep_key_file_exists():
83
141
  with open(Path(self.config_dir).joinpath(self.ep_key_file_name), "r") as f:
84
142
  api_key_from_cache = f.read().strip()
85
143
 
144
+ # Try to get key from environment variable
86
145
  api_key_from_os = os.getenv("EXPECTED_PARROT_API_KEY")
87
146
 
147
+ # Handle the case where both sources have keys
88
148
  if api_key_from_os and api_key_from_cache:
89
149
  if api_key_from_os != api_key_from_cache:
90
150
  import warnings
91
-
92
151
  warnings.warn(
93
152
  "WARNING: The Expected Parrot API key from the environment variable "
94
153
  "differs from the one stored in the config directory. Using the one "
95
154
  "from the environment variable."
96
155
  )
97
156
  api_key = api_key_from_os
98
-
99
- if api_key_from_os and not api_key_from_cache:
157
+ # Handle the case where only OS environment has key
158
+ elif api_key_from_os:
100
159
  api_key = api_key_from_os
101
-
102
- if not api_key_from_os and api_key_from_cache:
160
+ # Handle the case where only cached key exists
161
+ elif api_key_from_cache:
103
162
  api_key = api_key_from_cache
104
163
 
164
+ # If a key was found, ask to store it persistently
105
165
  if api_key is not None:
106
166
  _ = self.ask_to_store(api_key)
167
+
107
168
  return api_key
108
169
 
109
170
  def delete_ep_api_key(self):
@@ -112,7 +173,22 @@ class ExpectedParrotKeyHandler:
112
173
  os.remove(key_path)
113
174
  print("Deleted Expected Parrot API key at ", key_path)
114
175
 
115
- def store_ep_api_key(self, api_key):
176
+ def store_ep_api_key(self, api_key: str) -> None:
177
+ """
178
+ Store the Expected Parrot API key in the user's config directory.
179
+
180
+ This method saves the provided API key to a file in the platform-specific
181
+ user configuration directory, creating the directory if it doesn't exist.
182
+
183
+ Parameters:
184
+ api_key (str): The API key to store
185
+
186
+ Notes:
187
+ - The key is stored in plain text in the user's config directory
188
+ - The directory is created if it doesn't exist
189
+ - Any existing key file will be overwritten
190
+ - The location of the config directory is platform-specific
191
+ """
116
192
  # Create the directory if it doesn't exist
117
193
  os.makedirs(self.config_dir, exist_ok=True)
118
194
 
@@ -0,0 +1,62 @@
1
+ """
2
+ Exceptions specific to the Expected Parrot cloud service integration.
3
+
4
+ This module defines exception classes for various error conditions that can
5
+ occur when interacting with Expected Parrot's cloud services through the
6
+ Coop module. These exceptions help with specific error handling and reporting.
7
+ """
8
+
9
+ from ..base import BaseException
10
+
11
+
12
+ class CoopErrors(BaseException):
13
+ """
14
+ Base class for all Coop-related exceptions.
15
+
16
+ This is the parent class for all exceptions raised by the Coop module.
17
+ It inherits from EDSL's BaseException to maintain consistency with
18
+ the library's exception hierarchy.
19
+ """
20
+ pass
21
+
22
+
23
+ class CoopInvalidURLError(CoopErrors):
24
+ """
25
+ Exception raised when an invalid URL format is provided.
26
+
27
+ This exception is raised when a URL provided to the Coop client
28
+ does not match the expected format for object or resource URLs.
29
+
30
+ Example:
31
+ When a URL doesn't follow the pattern:
32
+ - https://expectedparrot.com/content/{uuid}
33
+ - https://expectedparrot.com/content/{username}/{alias}
34
+ """
35
+ pass
36
+
37
+
38
+ class CoopNoUUIDError(CoopErrors):
39
+ """
40
+ Exception raised when a required UUID or identifier is missing.
41
+
42
+ This exception is raised when an operation requires a UUID or other
43
+ identifier, but none was provided.
44
+
45
+ Example:
46
+ When calling get() without providing a UUID or URL
47
+ """
48
+ pass
49
+
50
+
51
+ class CoopServerResponseError(CoopErrors):
52
+ """
53
+ Exception raised when the server returns an error response.
54
+
55
+ This exception is raised when the Expected Parrot API returns an error
56
+ response, such as authentication failures, rate limits, or server errors.
57
+ The exception message typically includes the error details from the server.
58
+
59
+ Example:
60
+ When the server returns a 401 Unauthorized response due to an invalid API key
61
+ """
62
+ pass
@@ -0,0 +1,126 @@
1
+ """
2
+ Module for retrieving and caching language model pricing information.
3
+
4
+ This module provides functionality to fetch current pricing information for various
5
+ language models from the Expected Parrot API. It uses a singleton pattern to ensure
6
+ that price information is only fetched once and then cached for efficiency.
7
+ """
8
+
9
+ import requests
10
+ import csv
11
+ from io import StringIO
12
+ from typing import Dict, Tuple, Optional, Any
13
+
14
+
15
+ class PriceFetcher:
16
+ """
17
+ A singleton class for fetching and caching language model pricing information.
18
+
19
+ This class retrieves the current pricing for language models from the Expected
20
+ Parrot API and caches it to avoid unnecessary network requests. It implements
21
+ a singleton pattern to ensure that only one instance exists throughout the
22
+ application.
23
+
24
+ Attributes:
25
+ _instance (PriceFetcher): The singleton instance of the class
26
+ _cached_prices (Dict[Tuple[str, str], Dict]): Cached pricing information
27
+ mapping (service, model) tuples to their pricing details
28
+ """
29
+ _instance = None
30
+
31
+ def __new__(cls):
32
+ """
33
+ Create or return the singleton instance of PriceFetcher.
34
+
35
+ This method ensures that only one instance of PriceFetcher exists.
36
+ When called multiple times, it returns the same instance.
37
+
38
+ Returns:
39
+ PriceFetcher: The singleton instance
40
+ """
41
+ if cls._instance is None:
42
+ cls._instance = super(PriceFetcher, cls).__new__(cls)
43
+ cls._instance._cached_prices = None
44
+ return cls._instance
45
+
46
+ def fetch_prices(self) -> Dict[Tuple[str, str], Dict[str, Dict[str, Any]]]:
47
+ """
48
+ Fetch current pricing information for language models.
49
+
50
+ This method retrieves the latest pricing information from the Expected Parrot API
51
+ for all supported language models. It caches the results to avoid redundant API calls.
52
+
53
+ Returns:
54
+ Dict[Tuple[str, str], Dict[str, Dict[str, Any]]]: A dictionary mapping
55
+ (service, model) tuples to their pricing information for different token types.
56
+ Structure example:
57
+ {
58
+ ('openai', 'gpt-4'): {
59
+ 'input': {
60
+ 'service': 'openai',
61
+ 'model': 'gpt-4',
62
+ 'token_type': 'input',
63
+ 'usd_per_1M_tokens': 30.0,
64
+ ...
65
+ },
66
+ 'output': {
67
+ 'service': 'openai',
68
+ 'model': 'gpt-4',
69
+ 'token_type': 'output',
70
+ 'usd_per_1M_tokens': 60.0,
71
+ ...
72
+ }
73
+ },
74
+ ...
75
+ }
76
+
77
+ Notes:
78
+ - If the request fails, returns an empty dictionary
79
+ - Uses caching to avoid redundant API calls
80
+ - Pricing data includes both input and output token costs
81
+ - This method is automatically called by the Coop.fetch_prices() method
82
+ """
83
+ # Return cached prices if available
84
+ if self._cached_prices is not None:
85
+ return self._cached_prices
86
+
87
+ import os
88
+ import requests
89
+ from ..config import CONFIG
90
+
91
+ try:
92
+ # Fetch the pricing data
93
+ url = f"{CONFIG.EXPECTED_PARROT_URL}/api/v0/prices"
94
+ api_key = os.getenv("EXPECTED_PARROT_API_KEY")
95
+ headers = {}
96
+ if api_key:
97
+ headers["Authorization"] = f"Bearer {api_key}"
98
+ else:
99
+ headers["Authorization"] = f"Bearer None"
100
+
101
+ response = requests.get(url, headers=headers, timeout=20)
102
+ response.raise_for_status() # Raise an exception for bad responses
103
+
104
+ # Parse the data
105
+ data = response.json()
106
+
107
+ # Organize pricing data by (service, model) and token type
108
+ price_lookup = {}
109
+ for entry in data:
110
+ service = entry.get("service", None)
111
+ model = entry.get("model", None)
112
+ if service and model:
113
+ token_type = entry.get("token_type", None)
114
+ if (service, model) in price_lookup:
115
+ price_lookup[(service, model)].update({token_type: entry})
116
+ else:
117
+ price_lookup[(service, model)] = {token_type: entry}
118
+
119
+ # Cache and return the results
120
+ self._cached_prices = price_lookup
121
+ return self._cached_prices
122
+
123
+ except requests.RequestException as e:
124
+ # Silently handle errors and return empty dict
125
+ # print(f"An error occurred: {e}")
126
+ return {}
edsl/coop/utils.py CHANGED
@@ -1,18 +1,16 @@
1
1
  from typing import Literal, Optional, Type, Union
2
2
 
3
- from edsl.agents.Agent import Agent
4
- from edsl.agents.AgentList import AgentList
5
- from edsl.data.Cache import Cache
6
- from edsl.language_models.ModelList import ModelList
7
- from edsl.notebooks.Notebook import Notebook
8
- from edsl.results.Results import Results
9
- from edsl.scenarios.Scenario import Scenario
10
- from edsl.scenarios.ScenarioList import ScenarioList
11
- from edsl.surveys.Survey import Survey
12
- from edsl.study.Study import Study
13
-
14
- from edsl.language_models.LanguageModel import LanguageModel
15
- from edsl.questions.QuestionBase import QuestionBase
3
+ from ..agents import Agent, AgentList
4
+ from ..caching import Cache
5
+ from ..language_models import ModelList
6
+ from ..notebooks import Notebook
7
+ from ..results import Results
8
+ from ..scenarios import Scenario, ScenarioList
9
+ from ..surveys import Survey
10
+ from ..study import Study
11
+
12
+ from ..language_models import LanguageModel
13
+ from ..questions import QuestionBase
16
14
 
17
15
  EDSLObject = Union[
18
16
  Agent,
@@ -61,7 +59,23 @@ VisibilityType = Literal[
61
59
 
62
60
  class ObjectRegistry:
63
61
  """
64
- Utility class to map object types to database models.
62
+ Registry that maps between EDSL class types and their cloud storage object types.
63
+
64
+ This utility class maintains a bidirectional mapping between EDSL Python classes
65
+ (like Survey, Agent, Results) and their corresponding object type identifiers
66
+ used in the cloud storage system. It enables the proper serialization,
67
+ deserialization, and type checking for objects stored in Expected Parrot's
68
+ cloud services.
69
+
70
+ The registry is used by the Coop client to:
71
+ 1. Determine the correct object type when uploading EDSL objects
72
+ 2. Instantiate the correct class when downloading objects
73
+ 3. Validate that retrieved objects match expected types
74
+
75
+ Attributes:
76
+ objects (list): List of mappings between object types and EDSL classes
77
+ object_type_to_edsl_class (dict): Maps object type strings to EDSL classes
78
+ edsl_class_to_object_type (dict): Maps EDSL class names to object type strings
65
79
  """
66
80
 
67
81
  objects = [
@@ -78,6 +92,8 @@ class ObjectRegistry:
78
92
  {"object_type": "survey", "edsl_class": Survey},
79
93
  {"object_type": "study", "edsl_class": Study},
80
94
  ]
95
+
96
+ # Create mappings for efficient lookups
81
97
  object_type_to_edsl_class = {o["object_type"]: o["edsl_class"] for o in objects}
82
98
  edsl_class_to_object_type = {
83
99
  o["edsl_class"].__name__: o["object_type"] for o in objects
@@ -85,12 +101,36 @@ class ObjectRegistry:
85
101
 
86
102
  @classmethod
87
103
  def get_object_type_by_edsl_class(cls, edsl_object: EDSLObject) -> ObjectType:
104
+ """
105
+ Get the object type identifier for an EDSL class or instance.
106
+
107
+ This method determines the appropriate object type string for a given EDSL class
108
+ or instance, which is needed when storing the object in the cloud.
109
+
110
+ Parameters:
111
+ edsl_object (EDSLObject): An EDSL class (type) or instance
112
+
113
+ Returns:
114
+ ObjectType: The corresponding object type string (e.g., "survey", "agent")
115
+
116
+ Raises:
117
+ ValueError: If no mapping exists for the provided object
118
+
119
+ Notes:
120
+ - Special handling for Question classes, which all map to "question"
121
+ - Works with both class types and instances
122
+ """
123
+ # Handle both class objects and instances
88
124
  if isinstance(edsl_object, type):
89
125
  edsl_class_name = edsl_object.__name__
90
126
  else:
91
127
  edsl_class_name = type(edsl_object).__name__
128
+
129
+ # Special handling for question classes
92
130
  if edsl_class_name.startswith("Question"):
93
131
  edsl_class_name = "QuestionBase"
132
+
133
+ # Look up the object type
94
134
  object_type = cls.edsl_class_to_object_type.get(edsl_class_name)
95
135
  if object_type is None:
96
136
  raise ValueError(f"Object type not found for {edsl_object=}")
@@ -98,10 +138,25 @@ class ObjectRegistry:
98
138
 
99
139
  @classmethod
100
140
  def get_edsl_class_by_object_type(cls, object_type: ObjectType) -> EDSLObject:
101
- EDSL_object = cls.object_type_to_edsl_class.get(object_type)
102
- if EDSL_object is None:
141
+ """
142
+ Get the EDSL class for a given object type identifier.
143
+
144
+ This method returns the appropriate EDSL class for a given object type string,
145
+ which is needed when retrieving objects from the cloud.
146
+
147
+ Parameters:
148
+ object_type (ObjectType): The object type string (e.g., "survey", "agent")
149
+
150
+ Returns:
151
+ EDSLObject: The corresponding EDSL class
152
+
153
+ Raises:
154
+ ValueError: If no mapping exists for the provided object type
155
+ """
156
+ EDSL_class = cls.object_type_to_edsl_class.get(object_type)
157
+ if EDSL_class is None:
103
158
  raise ValueError(f"EDSL class not found for {object_type=}")
104
- return EDSL_object
159
+ return EDSL_class
105
160
 
106
161
  @classmethod
107
162
  def get_registry(
@@ -110,14 +165,24 @@ class ObjectRegistry:
110
165
  exclude_classes: Optional[list] = None,
111
166
  ) -> dict:
112
167
  """
113
- Return the registry of objects.
114
-
115
- Exclude objects that are already registered in subclass_registry.
116
- This allows the user to isolate Coop-only objects.
117
-
118
- Also exclude objects if their class name is in the exclude_classes list.
168
+ Get a filtered registry of EDSL classes.
169
+
170
+ This method returns a dictionary of EDSL classes, optionally excluding
171
+ classes that are already registered elsewhere or explicitly excluded.
172
+
173
+ Parameters:
174
+ subclass_registry (dict, optional): Dictionary of classes to exclude
175
+ because they are already registered elsewhere
176
+ exclude_classes (list, optional): List of class names to explicitly exclude
177
+
178
+ Returns:
179
+ dict: Dictionary mapping class names to EDSL classes
180
+
181
+ Notes:
182
+ - This method is useful for building registries of classes that
183
+ can be serialized and stored in the cloud
184
+ - It helps avoid duplicate registrations of classes
119
185
  """
120
-
121
186
  if subclass_registry is None:
122
187
  subclass_registry = {}
123
188
  if exclude_classes is None:
@@ -1,74 +1,7 @@
1
- from typing import NamedTuple, Dict, List, Optional, Any
2
- from dataclasses import dataclass, fields
1
+ """Data transfer models module wrapper for backward compatibility.
3
2
 
3
+ This module re-exports everything from the edsl.base.data_transfer_models module.
4
+ """
4
5
 
5
- class ModelInputs(NamedTuple):
6
- "This is what was send by the agent to the model"
7
- user_prompt: str
8
- system_prompt: str
9
- encoded_image: Optional[str] = None
10
-
11
-
12
- class EDSLOutput(NamedTuple):
13
- "This is the edsl dictionary that is returned by the model"
14
- answer: Any
15
- generated_tokens: str
16
- comment: Optional[str] = None
17
-
18
-
19
- class ModelResponse(NamedTuple):
20
- "This is the metadata that is returned by the model and includes info about the cache"
21
- response: dict
22
- cache_used: bool
23
- cache_key: str
24
- cached_response: Optional[Dict[str, Any]] = None
25
- cost: Optional[float] = None
26
-
27
-
28
- class AgentResponseDict(NamedTuple):
29
- edsl_dict: EDSLOutput
30
- model_inputs: ModelInputs
31
- model_outputs: ModelResponse
32
-
33
-
34
- class EDSLResultObjectInput(NamedTuple):
35
- generated_tokens: str
36
- question_name: str
37
- prompts: dict
38
- cached_response: str
39
- raw_model_response: str
40
- cache_used: bool
41
- cache_key: str
42
- answer: Any
43
- comment: str
44
- validated: bool = False
45
- exception_occurred: Exception = None
46
- cost: Optional[float] = None
47
-
48
-
49
- @dataclass
50
- class ImageInfo:
51
- file_path: str
52
- file_name: str
53
- image_format: str
54
- file_size: int
55
- encoded_image: str
56
-
57
- def __repr__(self):
58
- import reprlib
59
-
60
- reprlib_instance = reprlib.Repr()
61
- reprlib_instance.maxstring = 30 # Limit the string length for the encoded image
62
-
63
- # Get all fields except encoded_image
64
- field_reprs = [
65
- f"{f.name}={getattr(self, f.name)!r}"
66
- for f in fields(self)
67
- if f.name != "encoded_image"
68
- ]
69
-
70
- # Add the reprlib-restricted encoded_image field
71
- field_reprs.append(f"encoded_image={reprlib_instance.repr(self.encoded_image)}")
72
-
73
- # Join everything to create the repr
74
- return f"{self.__class__.__name__}({', '.join(field_reprs)})"
6
+ # Re-export everything from edsl.base.data_transfer_models module
7
+ from edsl.base.data_transfer_models import *
@@ -0,0 +1,10 @@
1
+ from .dataset import Dataset
2
+
3
+ from .dataset_operations_mixin import AgentListOperationsMixin
4
+ from .dataset_operations_mixin import ScenarioListOperationsMixin
5
+ from .dataset_operations_mixin import DatasetOperationsMixin
6
+ from .dataset_operations_mixin import ResultsOperationsMixin
7
+
8
+ __all__ = [
9
+ "Dataset",
10
+ ]