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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +303 -67
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +313 -167
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +4 -9
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +365 -220
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/{FileStore.py → file_store.py} +275 -189
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +18 -19
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/METADATA +1 -1
  230. edsl-0.1.48.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -493
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/coop/PriceFetcher.py +0 -54
  236. edsl/data/Cache.py +0 -582
  237. edsl/data/CacheEntry.py +0 -238
  238. edsl/data/SQLiteDict.py +0 -292
  239. edsl/data/__init__.py +0 -5
  240. edsl/data/orm.py +0 -10
  241. edsl/exceptions/cache.py +0 -5
  242. edsl/exceptions/coop.py +0 -14
  243. edsl/exceptions/data.py +0 -14
  244. edsl/exceptions/scenarios.py +0 -29
  245. edsl/jobs/Answers.py +0 -43
  246. edsl/jobs/JobsPrompts.py +0 -354
  247. edsl/jobs/buckets/BucketCollection.py +0 -134
  248. edsl/jobs/buckets/ModelBuckets.py +0 -65
  249. edsl/jobs/buckets/TokenBucket.py +0 -283
  250. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  251. edsl/jobs/interviews/Interview.py +0 -395
  252. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  253. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  254. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  255. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  256. edsl/jobs/tasks/TaskCreators.py +0 -64
  257. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  258. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  259. edsl/language_models/LanguageModel.py +0 -635
  260. edsl/language_models/ServiceDataSources.py +0 -0
  261. edsl/language_models/key_management/KeyLookup.py +0 -63
  262. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  263. edsl/language_models/key_management/models.py +0 -137
  264. edsl/questions/QuestionBase.py +0 -544
  265. edsl/questions/QuestionFreeText.py +0 -130
  266. edsl/questions/derived/QuestionLikertFive.py +0 -76
  267. edsl/results/ResultsExportMixin.py +0 -45
  268. edsl/results/TextEditor.py +0 -50
  269. edsl/results/results_fetch_mixin.py +0 -33
  270. edsl/results/results_tools_mixin.py +0 -98
  271. edsl/scenarios/DocumentChunker.py +0 -104
  272. edsl/scenarios/Scenario.py +0 -548
  273. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  274. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  275. edsl/scenarios/handlers/latex.py +0 -5
  276. edsl/shared.py +0 -1
  277. edsl/surveys/Survey.py +0 -1301
  278. edsl/surveys/SurveyQualtricsImport.py +0 -284
  279. edsl/surveys/SurveyToApp.py +0 -141
  280. edsl/surveys/instructions/__init__.py +0 -0
  281. edsl/tools/__init__.py +0 -1
  282. edsl/tools/clusters.py +0 -192
  283. edsl/tools/embeddings.py +0 -27
  284. edsl/tools/embeddings_plotting.py +0 -118
  285. edsl/tools/plotting.py +0 -112
  286. edsl/tools/summarize.py +0 -18
  287. edsl/utilities/data/Registry.py +0 -6
  288. edsl/utilities/data/__init__.py +0 -1
  289. edsl/utilities/data/scooter_results.json +0 -1
  290. edsl-0.1.47.dist-info/RECORD +0 -354
  291. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  292. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  293. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  294. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  295. /edsl/{results → dataset/display}/table_display.css +0 -0
  296. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  297. /edsl/{results → dataset}/tree_explore.py +0 -0
  298. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  299. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  300. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  301. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  302. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  303. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  304. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  305. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  306. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  307. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  308. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  309. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  310. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  311. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  312. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  313. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
  314. {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -1,15 +1,12 @@
1
1
  from typing import Optional, List
2
- from collections import UserDict
3
2
  import os
4
3
  from functools import lru_cache
5
- from dataclasses import dataclass, asdict
6
4
 
7
- from edsl.enums import service_to_api_keyname
8
- from edsl.exceptions.general import MissingAPIKeyError
5
+ from ..enums import service_to_api_keyname
6
+ from ..exceptions.general import MissingAPIKeyError
9
7
 
10
- from edsl.language_models.key_management.KeyLookup import KeyLookup
11
-
12
- from edsl.language_models.key_management.models import (
8
+ from .key_lookup import KeyLookup
9
+ from .models import (
13
10
  APIKeyEntry,
14
11
  LimitEntry,
15
12
  APIIDEntry,
@@ -32,31 +29,61 @@ api_id_to_service = {"AWS_ACCESS_KEY_ID": "bedrock"}
32
29
 
33
30
 
34
31
  class KeyLookupBuilder:
35
- """Builds KeyLookup options.
36
-
37
- >>> builder = KeyLookupBuilder(fetch_order=("config", "env"))
38
- >>> builder.DEFAULT_RPM
39
- 100
40
- >>> builder.DEFAULT_TPM
41
- 2000000
42
- >>> builder.fetch_order
43
- ('config', 'env')
44
-
45
- Test invalid fetch_order:
46
- >>> try:
47
- ... KeyLookupBuilder(fetch_order=["config", "env"]) # Should be tuple
48
- ... except ValueError as e:
49
- ... str(e)
50
- 'fetch_order must be a tuple'
51
-
52
- Test service extraction:
53
- >>> builder.extract_service("EDSL_SERVICE_RPM_OPENAI")
54
- ('openai', 'rpm')
32
+ """Factory class for building KeyLookup objects by gathering credentials from multiple sources.
33
+
34
+ KeyLookupBuilder is responsible for discovering, organizing, and consolidating API keys
35
+ and rate limits from various sources. It can pull credentials from:
36
+
37
+ - Environment variables (env)
38
+ - Configuration files (config)
39
+ - Remote services (coop)
40
+
41
+ The builder handles the complexities of:
42
+ - Finding API keys with different naming conventions
43
+ - Merging rate limits from different sources
44
+ - Processing additional credentials like API IDs
45
+ - Prioritizing sources based on a configurable order
46
+
47
+ Basic usage:
48
+ >>> builder = KeyLookupBuilder()
49
+ >>> keys = builder.build()
50
+ >>> # Now use keys to access service credentials
51
+ >>> keys['test'].api_token
52
+ 'test'
53
+
54
+ Customizing priorities:
55
+ >>> builder = KeyLookupBuilder(fetch_order=("config", "env"))
56
+ >>> builder.fetch_order
57
+ ('config', 'env')
58
+ >>> # 'env' has higher priority than 'config'
59
+
60
+ Configuration parameters:
61
+ >>> builder = KeyLookupBuilder()
62
+ >>> builder.DEFAULT_RPM # Default API calls per minute
63
+ 100
64
+ >>> builder.DEFAULT_TPM # Default tokens per minute
65
+ 2000000
66
+
67
+ Validation examples:
68
+ >>> try:
69
+ ... KeyLookupBuilder(fetch_order=["config", "env"]) # Should be tuple
70
+ ... except ValueError as e:
71
+ ... str(e)
72
+ 'fetch_order must be a tuple'
73
+
74
+ >>> builder = KeyLookupBuilder()
75
+ >>> builder.extract_service("EDSL_SERVICE_RPM_OPENAI")
76
+ ('openai', 'rpm')
77
+
78
+ Technical Notes:
79
+ - The fetch_order parameter controls priority (later sources override earlier ones)
80
+ - Default rate limits are applied when not explicitly provided
81
+ - Maintains tracking of where each value came from for debugging
55
82
  """
56
83
 
57
84
  # DEFAULT_RPM = 10
58
85
  # DEFAULT_TPM = 2000000
59
- from edsl.config import CONFIG
86
+ from ..config import CONFIG
60
87
 
61
88
  DEFAULT_RPM = int(CONFIG.get("EDSL_SERVICE_RPM_BASELINE"))
62
89
  DEFAULT_TPM = int(CONFIG.get("EDSL_SERVICE_TPM_BASELINE"))
@@ -66,7 +93,7 @@ class KeyLookupBuilder:
66
93
  fetch_order: Optional[tuple[str]] = None,
67
94
  coop: Optional["Coop"] = None,
68
95
  ):
69
- from edsl.coop import Coop
96
+ from ..coop import Coop
70
97
 
71
98
  # Fetch order goes from lowest priority to highest priority
72
99
  if fetch_order is None:
@@ -99,34 +126,72 @@ class KeyLookupBuilder:
99
126
 
100
127
  @lru_cache
101
128
  def build(self) -> "KeyLookup":
102
- """Build a KeyLookup instance.
103
-
104
- >>> builder = KeyLookupBuilder()
105
- >>> lookup = builder.build()
106
- >>> isinstance(lookup, KeyLookup)
107
- True
108
- >>> lookup['test'].api_token # Test service should always exist
109
- 'test'
129
+ """Build a KeyLookup instance with all discovered credentials.
130
+
131
+ Processes all discovered API keys and rate limits from the configured sources
132
+ and builds a KeyLookup instance containing LanguageModelInput objects for
133
+ each valid service. This method is cached, so subsequent calls will return
134
+ the same instance unless the builder state changes.
135
+
136
+ Returns:
137
+ KeyLookup: A populated KeyLookup instance with service credentials
138
+
139
+ Examples:
140
+ >>> builder = KeyLookupBuilder()
141
+ >>> lookup = builder.build()
142
+ >>> isinstance(lookup, KeyLookup)
143
+ True
144
+ >>> lookup['test'].api_token # Test service should always exist
145
+ 'test'
146
+
147
+ Technical Notes:
148
+ - Skips services with missing API keys
149
+ - Always includes a 'test' service for internal testing
150
+ - Uses lru_cache to avoid rebuilding unless necessary
151
+ - Each valid service gets a complete LanguageModelInput with
152
+ API token, rate limits, and optional API ID
110
153
  """
111
154
  d = {}
155
+ # Create entries for all discovered services
112
156
  for service in self.known_services:
113
157
  try:
114
158
  d[service] = self.get_language_model_input(service)
115
159
  except MissingAPIKeyError:
116
- pass
160
+ pass # Skip services with missing API keys
117
161
 
162
+ # Always include a test service
118
163
  d.update({"test": LanguageModelInput(api_token="test", rpm=10, tpm=2000000)})
119
164
  return KeyLookup(d)
120
165
 
121
166
  def get_language_model_input(self, service: str) -> LanguageModelInput:
122
- """Get the language model input for a given service.
123
-
124
- >>> builder = KeyLookupBuilder()
125
- >>> try:
126
- ... builder.get_language_model_input("nonexistent_service")
127
- ... except MissingAPIKeyError as e:
128
- ... str(e)
129
- "No key found for service 'nonexistent_service'"
167
+ """Construct a LanguageModelInput object for the specified service.
168
+
169
+ Creates a complete LanguageModelInput object for the requested service by
170
+ combining the API key, rate limits, and optional API ID from the various
171
+ data sources. This method assembles the disparate pieces of information
172
+ into a single configuration object.
173
+
174
+ Args:
175
+ service: Name of the service to retrieve configuration for (e.g., 'openai')
176
+
177
+ Returns:
178
+ LanguageModelInput: A configuration object with the service's credentials
179
+
180
+ Raises:
181
+ MissingAPIKeyError: If the required API key for the service is not found
182
+
183
+ Examples:
184
+ >>> builder = KeyLookupBuilder()
185
+ >>> try:
186
+ ... builder.get_language_model_input("nonexistent_service")
187
+ ... except MissingAPIKeyError as e:
188
+ ... str(e)
189
+ "No key found for service 'nonexistent_service'"
190
+
191
+ Technical Notes:
192
+ - Uses default rate limits if none are specified
193
+ - Preserves information about where each value came from
194
+ - Supports services that require both API key and API ID
130
195
  """
131
196
  if (key_entries := self.key_data.get(service)) is None:
132
197
  raise MissingAPIKeyError(f"No key found for service '{service}'")
@@ -173,7 +238,7 @@ class KeyLookupBuilder:
173
238
  return dict(list(self.coop.fetch_rate_limit_config_vars().items()))
174
239
 
175
240
  def _config_key_value_pairs(self):
176
- from edsl.config import CONFIG
241
+ from ..config import CONFIG
177
242
 
178
243
  return dict(list(CONFIG.items()))
179
244
 
@@ -302,3 +367,9 @@ class KeyLookupBuilder:
302
367
  def process_key_value_pairs(self) -> None:
303
368
  """Process all key-value pairs from the configured sources."""
304
369
  self.update_from_dict(self.get_key_value_pairs())
370
+
371
+
372
+ if __name__ == "__main__":
373
+ import doctest
374
+
375
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -0,0 +1,82 @@
1
+ from collections import UserDict
2
+
3
+ from .key_lookup_builder import KeyLookupBuilder
4
+ from .key_lookup import KeyLookup
5
+
6
+ class KeyLookupCollection(UserDict):
7
+ """Singleton collection for caching and reusing KeyLookup objects.
8
+
9
+ KeyLookupCollection implements the singleton pattern to provide a global registry
10
+ of KeyLookup objects. It avoids rebuilding KeyLookup objects with the same
11
+ fetch_order by storing and reusing previously created instances.
12
+
13
+ This collection serves several purposes:
14
+ - Reduces overhead by avoiding redundant API key discovery
15
+ - Ensures consistency by reusing the same credentials throughout an application
16
+ - Provides a central access point for credential configuration
17
+
18
+ The collection uses fetch_order tuples as keys and KeyLookup objects as values,
19
+ creating a new KeyLookup only when a new fetch_order is requested.
20
+
21
+ Singleton behavior:
22
+ >>> collection = KeyLookupCollection()
23
+ >>> collection2 = KeyLookupCollection()
24
+ >>> collection is collection2 # Same instance
25
+ True
26
+
27
+ Basic usage:
28
+ >>> collection = KeyLookupCollection()
29
+ >>> collection.add_key_lookup(("config", "env"))
30
+ >>> lookup = collection[("config", "env")] # Get the stored KeyLookup
31
+ >>> isinstance(lookup, KeyLookup)
32
+ True
33
+ >>> ("config", "env") in collection
34
+ True
35
+
36
+ Technical Notes:
37
+ - Uses __new__ to implement the singleton pattern
38
+ - Lazily creates KeyLookup objects only when requested
39
+ - Default fetch_order is ("config", "env") if not specified
40
+ """
41
+
42
+ _instance = None
43
+
44
+ def __new__(cls, *args, **kwargs):
45
+ if cls._instance is None:
46
+ cls._instance = super().__new__(cls)
47
+ return cls._instance
48
+
49
+ def __init__(self, *args, **kwargs):
50
+ if not hasattr(self, "_initialized"):
51
+ self.data = {}
52
+ self._initialized = True
53
+ super().__init__(*args, **kwargs)
54
+
55
+ def add_key_lookup(self, fetch_order=None):
56
+ """Add a KeyLookup to the collection with the specified fetch order.
57
+
58
+ Creates a new KeyLookup using the KeyLookupBuilder with the given fetch_order,
59
+ or uses a default fetch_order of ("config", "env") if none is provided.
60
+
61
+ The created KeyLookup is stored in the collection using the fetch_order as the key.
62
+ If a KeyLookup with the same fetch_order already exists, this method does nothing.
63
+
64
+ Args:
65
+ fetch_order: Tuple specifying the order of sources to fetch credentials from.
66
+ Later sources override earlier ones. If None, uses ("config", "env").
67
+
68
+ Examples:
69
+ >>> collection = KeyLookupCollection()
70
+ >>> collection.add_key_lookup(("config", "env", "coop"))
71
+ >>> ("config", "env", "coop") in collection
72
+ True
73
+
74
+ Technical Notes:
75
+ - KeyLookup objects are created lazily only when needed
76
+ - Sources can include: "config", "env", "coop"
77
+ - The fetch_order determines credential priority
78
+ """
79
+ if fetch_order is None:
80
+ fetch_order = ("config", "env")
81
+ if fetch_order not in self.data:
82
+ self.data[fetch_order] = KeyLookupBuilder(fetch_order=fetch_order).build()
@@ -0,0 +1,218 @@
1
+ from dataclasses import dataclass, asdict
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class APIKeyEntry:
7
+ """Data structure for storing API key information with metadata.
8
+
9
+ APIKeyEntry encapsulates an API key along with metadata about its service,
10
+ environment variable name, and the source it was retrieved from. This structure
11
+ allows for tracking and debugging the origin of API keys.
12
+
13
+ Attributes:
14
+ service: The service identifier (e.g., 'openai', 'anthropic')
15
+ name: The environment variable name (e.g., 'OPENAI_API_KEY')
16
+ value: The actual API key value
17
+ source: Where the key was obtained from (e.g., 'env', 'config')
18
+
19
+ Examples:
20
+ >>> entry = APIKeyEntry.example()
21
+ >>> entry.service
22
+ 'openai'
23
+ >>> entry.name
24
+ 'OPENAI_API_KEY'
25
+ >>> entry.value
26
+ 'sk-abcd1234'
27
+ >>> entry.source
28
+ 'env'
29
+
30
+ Technical Notes:
31
+ - Source values typically include: 'env', 'config', 'coop'
32
+ - Names follow the convention of the service's API documentation
33
+ """
34
+
35
+ service: str
36
+ name: str
37
+ value: str
38
+ source: Optional[str] = None
39
+
40
+ @classmethod
41
+ def example(cls):
42
+ return APIKeyEntry(
43
+ service="openai", name="OPENAI_API_KEY", value="sk-abcd1234", source="env"
44
+ )
45
+
46
+
47
+ @dataclass
48
+ class LimitEntry:
49
+ """Data structure for storing service rate limits with metadata.
50
+
51
+ LimitEntry encapsulates rate limit information for a service, including
52
+ requests per minute (rpm) and tokens per minute (tpm) limits. It also
53
+ tracks the source of each limit value to aid in debugging and understanding
54
+ configuration priority.
55
+
56
+ Attributes:
57
+ service: The service identifier (e.g., 'openai', 'anthropic')
58
+ rpm: Requests per minute limit
59
+ tpm: Tokens per minute limit
60
+ rpm_source: Where the rpm value was obtained from
61
+ tpm_source: Where the tpm value was obtained from
62
+
63
+ Examples:
64
+ >>> limit = LimitEntry.example()
65
+ >>> limit.service
66
+ 'openai'
67
+ >>> limit.rpm # Requests per minute
68
+ 60
69
+ >>> limit.tpm # Tokens per minute
70
+ 100000
71
+ >>> limit.rpm_source # Source of the RPM value
72
+ 'config'
73
+ >>> limit.tpm_source # Source of the TPM value
74
+ 'env'
75
+
76
+ Technical Notes:
77
+ - Source values typically include: 'env', 'config', 'coop', 'default'
78
+ - rpm and tpm can come from different sources
79
+ - Default values are applied when specific limits aren't found
80
+ """
81
+
82
+ service: str
83
+ rpm: int
84
+ tpm: int
85
+ rpm_source: Optional[str] = None
86
+ tpm_source: Optional[str] = None
87
+
88
+ @classmethod
89
+ def example(cls):
90
+ return LimitEntry(
91
+ service="openai", rpm=60, tpm=100000, rpm_source="config", tpm_source="env"
92
+ )
93
+
94
+
95
+ @dataclass
96
+ class APIIDEntry:
97
+ """Data structure for storing API ID information with metadata.
98
+
99
+ APIIDEntry encapsulates an API ID (like AWS Access Key ID) along with
100
+ metadata about its service, environment variable name, and source. Some
101
+ services like AWS Bedrock require both an API key and an API ID.
102
+
103
+ Attributes:
104
+ service: The service identifier (e.g., 'bedrock' for AWS)
105
+ name: The environment variable name (e.g., 'AWS_ACCESS_KEY_ID')
106
+ value: The actual API ID value
107
+ source: Where the ID was obtained from (e.g., 'env', 'config')
108
+
109
+ Examples:
110
+ >>> id_entry = APIIDEntry.example()
111
+ >>> id_entry.service
112
+ 'bedrock'
113
+ >>> id_entry.name
114
+ 'AWS_ACCESS_KEY_ID'
115
+ >>> id_entry.value
116
+ 'AKIA1234'
117
+ >>> id_entry.source
118
+ 'env'
119
+
120
+ Technical Notes:
121
+ - Currently primarily used for AWS Bedrock integration
122
+ - Follows the same pattern as APIKeyEntry for consistency
123
+ - Source tracking helps with debugging configuration issues
124
+ """
125
+
126
+ service: str
127
+ name: str
128
+ value: str
129
+ source: Optional[str] = None
130
+
131
+ @classmethod
132
+ def example(cls):
133
+ return APIIDEntry(
134
+ service="bedrock", name="AWS_ACCESS_KEY_ID", value="AKIA1234", source="env"
135
+ )
136
+
137
+
138
+ @dataclass
139
+ class LanguageModelInput:
140
+ """Comprehensive configuration for a language model service.
141
+
142
+ LanguageModelInput brings together all the configuration needed to interact with
143
+ a language model service, including authentication credentials and rate limits.
144
+ This is the primary data structure used by the language_models module to access
145
+ service credentials in a unified way.
146
+
147
+ The class combines:
148
+ - API token for authentication
149
+ - Rate limits (rpm, tpm)
150
+ - Optional API ID for services that require it
151
+ - Source tracking for all values
152
+
153
+ Attributes:
154
+ api_token: The API key/token for authentication
155
+ rpm: Requests per minute limit
156
+ tpm: Tokens per minute limit
157
+ api_id: Optional secondary ID (e.g., AWS Access Key ID)
158
+ token_source: Where the API token was obtained from
159
+ rpm_source: Where the rpm value was obtained from
160
+ tpm_source: Where the tpm value was obtained from
161
+ id_source: Where the api_id was obtained from
162
+
163
+ Basic usage:
164
+ >>> lm_input = LanguageModelInput(api_token='sk-key123', rpm=60, tpm=100000)
165
+ >>> lm_input.api_token
166
+ 'sk-key123'
167
+
168
+ Example instance:
169
+ >>> lm_input = LanguageModelInput.example()
170
+ >>> lm_input.api_token
171
+ 'sk-abcd123'
172
+ >>> lm_input.rpm
173
+ 60
174
+ >>> lm_input.tpm
175
+ 100000
176
+ >>> lm_input.api_id # None for most services
177
+
178
+ Serialization:
179
+ >>> d = lm_input.to_dict()
180
+ >>> isinstance(d, dict)
181
+ True
182
+ >>> LanguageModelInput.from_dict(d).api_token == lm_input.api_token
183
+ True
184
+
185
+ Technical Notes:
186
+ - Used as values in the KeyLookup dictionary
187
+ - Centralizes all service configuration in one object
188
+ - Supports serialization for storage and transmission
189
+ - Preserves metadata about configuration sources
190
+ """
191
+
192
+ api_token: str
193
+ rpm: int
194
+ tpm: int
195
+ api_id: Optional[str] = None
196
+ token_source: Optional[str] = None
197
+ rpm_source: Optional[str] = None
198
+ tpm_source: Optional[str] = None
199
+ id_source: Optional[str] = None
200
+
201
+ def to_dict(self):
202
+ return asdict(self)
203
+
204
+ @classmethod
205
+ def from_dict(cls, d):
206
+ return cls(**d)
207
+
208
+ @classmethod
209
+ def example(cls):
210
+ return LanguageModelInput(
211
+ api_token="sk-abcd123", tpm=100000, rpm=60, api_id=None
212
+ )
213
+
214
+
215
+ if __name__ == "__main__":
216
+ import doctest
217
+
218
+ doctest.testmod()
@@ -1,2 +1,7 @@
1
- from edsl.language_models.LanguageModel import LanguageModel
2
- from edsl.language_models.model import Model
1
+ from .language_model import LanguageModel
2
+ from .model import Model
3
+ from .model_list import ModelList
4
+
5
+ from .exceptions import LanguageModelBadResponseError
6
+
7
+ __all__ = ["Model", "ModelList", "LanguageModelBadResponseError"]
@@ -1,7 +1,16 @@
1
- from typing import Any, Union
1
+ from typing import Any, Union, TYPE_CHECKING
2
2
 
3
+ if TYPE_CHECKING:
4
+ from .language_model import LanguageModel
3
5
 
4
6
  class ComputeCost:
7
+ """Computes the dollar cost of a raw response.
8
+
9
+ # TODO: Add doctests
10
+ >>> True
11
+ True
12
+
13
+ """
5
14
  def __init__(self, language_model: "LanguageModel"):
6
15
  self.language_model = language_model
7
16
  self._price_lookup = None
@@ -9,7 +18,7 @@ class ComputeCost:
9
18
  @property
10
19
  def price_lookup(self):
11
20
  if self._price_lookup is None:
12
- from edsl.coop import Coop
21
+ from ..coop import Coop
13
22
 
14
23
  c = Coop()
15
24
  self._price_lookup = c.fetch_prices()
@@ -19,7 +28,7 @@ class ComputeCost:
19
28
  """Return the dollar cost of a raw response."""
20
29
 
21
30
  usage = self.get_usage_dict(raw_response)
22
- from edsl.coop import Coop
31
+ from ..coop import Coop
23
32
 
24
33
  c = Coop()
25
34
  price_lookup = c.fetch_prices()
@@ -61,3 +70,9 @@ class ComputeCost:
61
70
  return f"Could not compute output price - {e}"
62
71
 
63
72
  return input_cost + output_cost
73
+
74
+
75
+
76
+ if __name__ == "__main__":
77
+ import doctest
78
+ doctest.testmod()
@@ -1,8 +1,9 @@
1
1
  from textwrap import dedent
2
2
  from typing import Optional
3
3
 
4
+ from ..base import BaseException
4
5
 
5
- class LanguageModelExceptions(Exception):
6
+ class LanguageModelExceptions(BaseException):
6
7
  explanation = (
7
8
  "This is the base class for all exceptions in the LanguageModel class."
8
9
  )