edsl 0.1.47__py3-none-any.whl → 0.1.49__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 +311 -75
  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.49.dist-info}/METADATA +1 -1
  230. edsl-0.1.49.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.49.dist-info}/LICENSE +0 -0
  314. {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/WHEEL +0 -0
@@ -1,635 +0,0 @@
1
- """This module contains the LanguageModel class, which is an abstract base class for all language models.
2
-
3
- Terminology:
4
-
5
- raw_response: The JSON response from the model. This has all the model meta-data about the call.
6
-
7
- edsl_augmented_response: The JSON response from model, but augmented with EDSL-specific information,
8
- such as the cache key, token usage, etc.
9
-
10
- generated_tokens: The actual tokens generated by the model. This is the output that is used by the user.
11
- edsl_answer_dict: The parsed JSON response from the model either {'answer': ...} or {'answer': ..., 'comment': ...}
12
-
13
- """
14
-
15
- from __future__ import annotations
16
- import warnings
17
- from functools import wraps
18
- import asyncio
19
- import json
20
- import os
21
- from typing import (
22
- Coroutine,
23
- Any,
24
- Type,
25
- Union,
26
- List,
27
- get_type_hints,
28
- TypedDict,
29
- Optional,
30
- TYPE_CHECKING,
31
- )
32
- from abc import ABC, abstractmethod
33
-
34
- from edsl.data_transfer_models import (
35
- ModelResponse,
36
- ModelInputs,
37
- EDSLOutput,
38
- AgentResponseDict,
39
- )
40
-
41
- if TYPE_CHECKING:
42
- from edsl.data.Cache import Cache
43
- from edsl.scenarios.FileStore import FileStore
44
- from edsl.questions.QuestionBase import QuestionBase
45
- from edsl.language_models.key_management.KeyLookup import KeyLookup
46
-
47
- from edsl.enums import InferenceServiceType
48
-
49
- from edsl.utilities.decorators import (
50
- sync_wrapper,
51
- jupyter_nb_handler,
52
- )
53
- from edsl.utilities.remove_edsl_version import remove_edsl_version
54
-
55
- from edsl.Base import PersistenceMixin, RepresentationMixin
56
- from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
57
-
58
- from edsl.language_models.key_management.KeyLookupCollection import (
59
- KeyLookupCollection,
60
- )
61
-
62
- from edsl.language_models.RawResponseHandler import RawResponseHandler
63
-
64
-
65
- def handle_key_error(func):
66
- """Handle KeyError exceptions."""
67
-
68
- @wraps(func)
69
- def wrapper(*args, **kwargs):
70
- try:
71
- return func(*args, **kwargs)
72
- assert True == False
73
- except KeyError as e:
74
- return f"""KeyError occurred: {e}. This is most likely because the model you are using
75
- returned a JSON object we were not expecting."""
76
-
77
- return wrapper
78
-
79
-
80
- class classproperty:
81
- def __init__(self, method):
82
- self.method = method
83
-
84
- def __get__(self, instance, cls):
85
- return self.method(cls)
86
-
87
-
88
- from edsl.Base import HashingMixin
89
-
90
-
91
- class LanguageModel(
92
- PersistenceMixin,
93
- RepresentationMixin,
94
- HashingMixin,
95
- ABC,
96
- metaclass=RegisterLanguageModelsMeta,
97
- ):
98
- """ABC for Language Models."""
99
-
100
- _model_ = None
101
- key_sequence = (
102
- None # This should be something like ["choices", 0, "message", "content"]
103
- )
104
-
105
- DEFAULT_RPM = 100
106
- DEFAULT_TPM = 1000
107
-
108
- @classproperty
109
- def response_handler(cls):
110
- key_sequence = cls.key_sequence
111
- usage_sequence = cls.usage_sequence if hasattr(cls, "usage_sequence") else None
112
- return RawResponseHandler(key_sequence, usage_sequence)
113
-
114
- def __init__(
115
- self,
116
- tpm: Optional[float] = None,
117
- rpm: Optional[float] = None,
118
- omit_system_prompt_if_empty_string: bool = True,
119
- key_lookup: Optional["KeyLookup"] = None,
120
- **kwargs,
121
- ):
122
- """Initialize the LanguageModel."""
123
- self.model = getattr(self, "_model_", None)
124
- default_parameters = getattr(self, "_parameters_", None)
125
- parameters = self._overide_default_parameters(kwargs, default_parameters)
126
- self.parameters = parameters
127
- self.remote = False
128
- self.omit_system_prompt_if_empty = omit_system_prompt_if_empty_string
129
-
130
- self.key_lookup = self._set_key_lookup(key_lookup)
131
- self.model_info = self.key_lookup.get(self._inference_service_)
132
-
133
- if rpm is not None:
134
- self._rpm = rpm
135
-
136
- if tpm is not None:
137
- self._tpm = tpm
138
-
139
- for key, value in parameters.items():
140
- setattr(self, key, value)
141
-
142
- for key, value in kwargs.items():
143
- if key not in parameters:
144
- setattr(self, key, value)
145
-
146
- if kwargs.get("skip_api_key_check", False):
147
- # Skip the API key check. Sometimes this is useful for testing.
148
- self._api_token = None
149
-
150
- def _set_key_lookup(self, key_lookup: "KeyLookup") -> "KeyLookup":
151
- """Set the key lookup."""
152
- if key_lookup is not None:
153
- return key_lookup
154
- else:
155
- klc = KeyLookupCollection()
156
- klc.add_key_lookup(fetch_order=("config", "env"))
157
- return klc.get(("config", "env"))
158
-
159
- def set_key_lookup(self, key_lookup: "KeyLookup") -> None:
160
- """Set the key lookup, later"""
161
- if hasattr(self, "_api_token"):
162
- del self._api_token
163
- self.key_lookup = key_lookup
164
-
165
- def ask_question(self, question: "QuestionBase") -> str:
166
- """Ask a question and return the response.
167
-
168
- :param question: The question to ask.
169
- """
170
- user_prompt = question.get_instructions().render(question.data).text
171
- system_prompt = "You are a helpful agent pretending to be a human."
172
- return self.execute_model_call(user_prompt, system_prompt)
173
-
174
- @property
175
- def rpm(self):
176
- if not hasattr(self, "_rpm"):
177
- if self.model_info is None:
178
- self._rpm = self.DEFAULT_RPM
179
- else:
180
- self._rpm = self.model_info.rpm
181
- return self._rpm
182
-
183
- @property
184
- def tpm(self):
185
- if not hasattr(self, "_tpm"):
186
- if self.model_info is None:
187
- self._tpm = self.DEFAULT_TPM
188
- else:
189
- self._tpm = self.model_info.tpm
190
- return self._tpm
191
-
192
- # in case we want to override the default values
193
- @tpm.setter
194
- def tpm(self, value):
195
- self._tpm = value
196
-
197
- @rpm.setter
198
- def rpm(self, value):
199
- self._rpm = value
200
-
201
- @property
202
- def api_token(self) -> str:
203
- if not hasattr(self, "_api_token"):
204
- info = self.key_lookup.get(self._inference_service_, None)
205
- if info is None:
206
- raise ValueError(
207
- f"No key found for service '{self._inference_service_}'"
208
- )
209
- self._api_token = info.api_token
210
- return self._api_token
211
-
212
- def __getitem__(self, key):
213
- return getattr(self, key)
214
-
215
- def hello(self, verbose=False):
216
- """Runs a simple test to check if the model is working."""
217
- token = self.api_token
218
- masked = token[: min(8, len(token))] + "..."
219
- if verbose:
220
- print(f"Current key is {masked}")
221
- return self.execute_model_call(
222
- user_prompt="Hello, model!", system_prompt="You are a helpful agent."
223
- )
224
-
225
- def has_valid_api_key(self) -> bool:
226
- """Check if the model has a valid API key.
227
-
228
- >>> LanguageModel.example().has_valid_api_key() : # doctest: +SKIP
229
- True
230
-
231
- This method is used to check if the model has a valid API key.
232
- """
233
- from edsl.enums import service_to_api_keyname
234
-
235
- if self._model_ == "test":
236
- return True
237
-
238
- key_name = service_to_api_keyname.get(self._inference_service_, "NOT FOUND")
239
- key_value = os.getenv(key_name)
240
- return key_value is not None
241
-
242
- def __hash__(self) -> str:
243
- """Allow the model to be used as a key in a dictionary.
244
-
245
- >>> m = LanguageModel.example()
246
- >>> hash(m)
247
- 325654563661254408
248
- """
249
- from edsl.utilities.utilities import dict_hash
250
-
251
- return dict_hash(self.to_dict(add_edsl_version=False))
252
-
253
- def __eq__(self, other) -> bool:
254
- """Check is two models are the same.
255
-
256
- >>> m1 = LanguageModel.example()
257
- >>> m2 = LanguageModel.example()
258
- >>> m1 == m2
259
- True
260
-
261
- """
262
- return self.model == other.model and self.parameters == other.parameters
263
-
264
- @staticmethod
265
- def _overide_default_parameters(passed_parameter_dict, default_parameter_dict):
266
- """Return a dictionary of parameters, with passed parameters taking precedence over defaults.
267
-
268
- >>> LanguageModel._overide_default_parameters(passed_parameter_dict={"temperature": 0.5}, default_parameter_dict={"temperature":0.9})
269
- {'temperature': 0.5}
270
- >>> LanguageModel._overide_default_parameters(passed_parameter_dict={"temperature": 0.5}, default_parameter_dict={"temperature":0.9, "max_tokens": 1000})
271
- {'temperature': 0.5, 'max_tokens': 1000}
272
- """
273
- # this is the case when data is loaded from a dict after serialization
274
- if "parameters" in passed_parameter_dict:
275
- passed_parameter_dict = passed_parameter_dict["parameters"]
276
- return {
277
- parameter_name: passed_parameter_dict.get(parameter_name, default_value)
278
- for parameter_name, default_value in default_parameter_dict.items()
279
- }
280
-
281
- def __call__(self, user_prompt: str, system_prompt: str):
282
- return self.execute_model_call(user_prompt, system_prompt)
283
-
284
- @abstractmethod
285
- async def async_execute_model_call(user_prompt: str, system_prompt: str):
286
- """Execute the model call and returns a coroutine."""
287
- pass
288
-
289
- async def remote_async_execute_model_call(
290
- self, user_prompt: str, system_prompt: str
291
- ):
292
- """Execute the model call and returns the result as a coroutine, using Coop."""
293
- from edsl.coop import Coop
294
-
295
- client = Coop()
296
- response_data = await client.remote_async_execute_model_call(
297
- self.to_dict(), user_prompt, system_prompt
298
- )
299
- return response_data
300
-
301
- @jupyter_nb_handler
302
- def execute_model_call(self, *args, **kwargs) -> Coroutine:
303
- """Execute the model call and returns the result as a coroutine."""
304
-
305
- async def main():
306
- results = await asyncio.gather(
307
- self.async_execute_model_call(*args, **kwargs)
308
- )
309
- return results[0] # Since there's only one task, return its result
310
-
311
- return main()
312
-
313
- @classmethod
314
- def get_generated_token_string(cls, raw_response: dict[str, Any]) -> str:
315
- """Return the generated token string from the raw response.
316
-
317
- >>> m = LanguageModel.example(test_model = True)
318
- >>> raw_response = m.execute_model_call("Hello, model!", "You are a helpful agent.")
319
- >>> m.get_generated_token_string(raw_response)
320
- 'Hello world'
321
-
322
- """
323
- return cls.response_handler.get_generated_token_string(raw_response)
324
-
325
- @classmethod
326
- def get_usage_dict(cls, raw_response: dict[str, Any]) -> dict[str, Any]:
327
- """Return the usage dictionary from the raw response."""
328
- return cls.response_handler.get_usage_dict(raw_response)
329
-
330
- @classmethod
331
- def parse_response(cls, raw_response: dict[str, Any]) -> EDSLOutput:
332
- """Parses the API response and returns the response text."""
333
- return cls.response_handler.parse_response(raw_response)
334
-
335
- async def _async_get_intended_model_call_outcome(
336
- self,
337
- user_prompt: str,
338
- system_prompt: str,
339
- cache: Cache,
340
- iteration: int = 0,
341
- files_list: Optional[List[FileStore]] = None,
342
- invigilator=None,
343
- ) -> ModelResponse:
344
- """Handle caching of responses.
345
-
346
- :param user_prompt: The user's prompt.
347
- :param system_prompt: The system's prompt.
348
- :param iteration: The iteration number.
349
- :param cache: The cache to use.
350
- :param files_list: The list of files to use.
351
- :param invigilator: The invigilator to use.
352
-
353
- If the cache isn't being used, it just returns a 'fresh' call to the LLM.
354
- But if cache is being used, it first checks the database to see if the response is already there.
355
- If it is, it returns the cached response, but again appends some tracking information.
356
- If it isn't, it calls the LLM, saves the response to the database, and returns the response with tracking information.
357
-
358
- If self.use_cache is True, then attempts to retrieve the response from the database;
359
- if not in the DB, calls the LLM and writes the response to the DB.
360
-
361
- >>> from edsl import Cache
362
- >>> m = LanguageModel.example(test_model = True)
363
- >>> m._get_intended_model_call_outcome(user_prompt = "Hello", system_prompt = "hello", cache = Cache())
364
- ModelResponse(...)"""
365
-
366
- if files_list:
367
- files_hash = "+".join([str(hash(file)) for file in files_list])
368
- user_prompt_with_hashes = user_prompt + f" {files_hash}"
369
- else:
370
- user_prompt_with_hashes = user_prompt
371
-
372
- cache_call_params = {
373
- "model": str(self.model),
374
- "parameters": self.parameters,
375
- "system_prompt": system_prompt,
376
- "user_prompt": user_prompt_with_hashes,
377
- "iteration": iteration,
378
- }
379
- cached_response, cache_key = cache.fetch(**cache_call_params)
380
-
381
- if cache_used := cached_response is not None:
382
- # print("cache used")
383
- response = json.loads(cached_response)
384
- else:
385
- # print("cache not used")
386
- f = (
387
- self.remote_async_execute_model_call
388
- if hasattr(self, "remote") and self.remote
389
- else self.async_execute_model_call
390
- )
391
- params = {
392
- "user_prompt": user_prompt,
393
- "system_prompt": system_prompt,
394
- "files_list": files_list,
395
- }
396
- from edsl.config import CONFIG
397
-
398
- TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
399
- response = await asyncio.wait_for(f(**params), timeout=TIMEOUT)
400
- new_cache_key = cache.store(
401
- **cache_call_params, response=response, service=self._inference_service_
402
- ) # store the response in the cache
403
- assert new_cache_key == cache_key # should be the same
404
-
405
- # breakpoint()
406
-
407
- cost = self.cost(response)
408
- # breakpoint()
409
- return ModelResponse(
410
- response=response,
411
- cache_used=cache_used,
412
- cache_key=cache_key,
413
- cached_response=cached_response,
414
- cost=cost,
415
- )
416
-
417
- _get_intended_model_call_outcome = sync_wrapper(
418
- _async_get_intended_model_call_outcome
419
- )
420
-
421
- def simple_ask(
422
- self,
423
- question: QuestionBase,
424
- system_prompt="You are a helpful agent pretending to be a human.",
425
- top_logprobs=2,
426
- ):
427
- """Ask a question and return the response."""
428
- self.logprobs = True
429
- self.top_logprobs = top_logprobs
430
- return self.execute_model_call(
431
- user_prompt=question.human_readable(), system_prompt=system_prompt
432
- )
433
-
434
- async def async_get_response(
435
- self,
436
- user_prompt: str,
437
- system_prompt: str,
438
- cache: Cache,
439
- iteration: int = 1,
440
- files_list: Optional[List[FileStore]] = None,
441
- **kwargs,
442
- ) -> dict:
443
- """Get response, parse, and return as string.
444
-
445
- :param user_prompt: The user's prompt.
446
- :param system_prompt: The system's prompt.
447
- :param cache: The cache to use.
448
- :param iteration: The iteration number.
449
- :param files_list: The list of files to use.
450
-
451
- """
452
- params = {
453
- "user_prompt": user_prompt,
454
- "system_prompt": system_prompt,
455
- "iteration": iteration,
456
- "cache": cache,
457
- "files_list": files_list,
458
- }
459
- if "invigilator" in kwargs:
460
- params.update({"invigilator": kwargs["invigilator"]})
461
-
462
- model_inputs = ModelInputs(user_prompt=user_prompt, system_prompt=system_prompt)
463
- model_outputs: ModelResponse = (
464
- await self._async_get_intended_model_call_outcome(**params)
465
- )
466
- edsl_dict: EDSLOutput = self.parse_response(model_outputs.response)
467
-
468
- agent_response_dict = AgentResponseDict(
469
- model_inputs=model_inputs,
470
- model_outputs=model_outputs,
471
- edsl_dict=edsl_dict,
472
- )
473
- # breakpoint()
474
- return agent_response_dict
475
-
476
- get_response = sync_wrapper(async_get_response)
477
-
478
- def cost(self, raw_response: dict[str, Any]) -> Union[float, str]:
479
- """Return the dollar cost of a raw response.
480
-
481
- :param raw_response: The raw response from the model.
482
- """
483
-
484
- usage = self.get_usage_dict(raw_response)
485
- from edsl.language_models.PriceManager import PriceManager
486
-
487
- price_manger = PriceManager()
488
- return price_manger.calculate_cost(
489
- inference_service=self._inference_service_,
490
- model=self.model,
491
- usage=usage,
492
- input_token_name=self.input_token_name,
493
- output_token_name=self.output_token_name,
494
- )
495
-
496
- def to_dict(self, add_edsl_version: bool = True) -> dict[str, Any]:
497
- """Convert instance to a dictionary
498
-
499
- :param add_edsl_version: Whether to add the EDSL version to the dictionary.
500
-
501
- >>> m = LanguageModel.example()
502
- >>> m.to_dict()
503
- {'model': '...', 'parameters': {'temperature': ..., 'max_tokens': ..., 'top_p': ..., 'frequency_penalty': ..., 'presence_penalty': ..., 'logprobs': False, 'top_logprobs': ...}, 'inference_service': 'openai', 'edsl_version': '...', 'edsl_class_name': 'LanguageModel'}
504
- """
505
- d = {
506
- "model": self.model,
507
- "parameters": self.parameters,
508
- "inference_service": self._inference_service_,
509
- }
510
- if add_edsl_version:
511
- from edsl import __version__
512
-
513
- d["edsl_version"] = __version__
514
- d["edsl_class_name"] = self.__class__.__name__
515
- return d
516
-
517
- @classmethod
518
- @remove_edsl_version
519
- def from_dict(cls, data: dict) -> Type[LanguageModel]:
520
- """Convert dictionary to a LanguageModel child instance.
521
-
522
- NB: This method does not use the stores inference_service but rather just fetches a model class based on the name.
523
- """
524
- from edsl.language_models.model import get_model_class
525
-
526
- model_class = get_model_class(
527
- data["model"], service_name=data.get("inference_service", None)
528
- )
529
- return model_class(**data)
530
-
531
- def __repr__(self) -> str:
532
- """Return a representation of the object."""
533
- param_string = ", ".join(
534
- f"{key} = {value}" for key, value in self.parameters.items()
535
- )
536
- return (
537
- f"Model(model_name = '{self.model}'"
538
- + (f", {param_string}" if param_string else "")
539
- + ")"
540
- )
541
-
542
- def __add__(self, other_model: Type[LanguageModel]) -> Type[LanguageModel]:
543
- """Combine two models into a single model (other_model takes precedence over self)."""
544
- import warnings
545
-
546
- warnings.warn(
547
- f"""Warning: one model is replacing another. If you want to run both models, use a single `by` e.g.,
548
- by(m1, m2, m3) not by(m1).by(m2).by(m3)."""
549
- )
550
- return other_model or self
551
-
552
- @classmethod
553
- def example(
554
- cls,
555
- test_model: bool = False,
556
- canned_response: str = "Hello world",
557
- throw_exception: bool = False,
558
- ) -> LanguageModel:
559
- """Return a default instance of the class.
560
-
561
- >>> from edsl.language_models import LanguageModel
562
- >>> m = LanguageModel.example(test_model = True, canned_response = "WOWZA!")
563
- >>> isinstance(m, LanguageModel)
564
- True
565
- >>> from edsl import QuestionFreeText
566
- >>> q = QuestionFreeText(question_text = "What is your name?", question_name = 'example')
567
- >>> q.by(m).run(cache = False, disable_remote_cache = True, disable_remote_inference = True).select('example').first()
568
- 'WOWZA!'
569
- >>> m = LanguageModel.example(test_model = True, canned_response = "WOWZA!", throw_exception = True)
570
- >>> r = q.by(m).run(cache = False, disable_remote_cache = True, disable_remote_inference = True, print_exceptions = True)
571
- Exception report saved to ...
572
- """
573
- from edsl.language_models.model import Model
574
-
575
- if test_model:
576
- m = Model(
577
- "test", canned_response=canned_response, throw_exception=throw_exception
578
- )
579
- return m
580
- else:
581
- return Model(skip_api_key_check=True)
582
-
583
- def from_cache(self, cache: "Cache") -> LanguageModel:
584
- from copy import deepcopy
585
- from types import MethodType
586
- from edsl import Cache
587
-
588
- new_instance = deepcopy(self)
589
- print("Cache entries", len(cache))
590
- new_instance.cache = Cache(
591
- data={k: v for k, v in cache.items() if v.model == self.model}
592
- )
593
- print("Cache entries with same model", len(new_instance.cache))
594
-
595
- new_instance.user_prompts = [
596
- ce.user_prompt for ce in new_instance.cache.values()
597
- ]
598
- new_instance.system_prompts = [
599
- ce.system_prompt for ce in new_instance.cache.values()
600
- ]
601
-
602
- async def async_execute_model_call(self, user_prompt: str, system_prompt: str):
603
- cache_call_params = {
604
- "model": str(self.model),
605
- "parameters": self.parameters,
606
- "system_prompt": system_prompt,
607
- "user_prompt": user_prompt,
608
- "iteration": 1,
609
- }
610
- cached_response, cache_key = cache.fetch(**cache_call_params)
611
- response = json.loads(cached_response)
612
- cost = 0
613
- return ModelResponse(
614
- response=response,
615
- cache_used=True,
616
- cache_key=cache_key,
617
- cached_response=cached_response,
618
- cost=cost,
619
- )
620
-
621
- # Bind the new method to the copied instance
622
- setattr(
623
- new_instance,
624
- "async_execute_model_call",
625
- MethodType(async_execute_model_call, new_instance),
626
- )
627
-
628
- return new_instance
629
-
630
-
631
- if __name__ == "__main__":
632
- """Run the module's test suite."""
633
- import doctest
634
-
635
- doctest.testmod(optionflags=doctest.ELLIPSIS)
File without changes
@@ -1,63 +0,0 @@
1
- from collections import UserDict
2
- from dataclasses import asdict
3
- from edsl.enums import service_to_api_keyname
4
-
5
- from edsl.language_models.key_management.models import LanguageModelInput
6
-
7
-
8
- class KeyLookup(UserDict):
9
- """A class for looking up API keys and related configuration.
10
-
11
- >>> from edsl.language_models.key_management.models import LanguageModelInput
12
- >>> lookup = KeyLookup()
13
- >>> lm_input = LanguageModelInput.example()
14
- >>> lookup['test'] = lm_input
15
- >>> lookup.to_dict()['test']['api_token']
16
- 'sk-abcd123'
17
- >>> restored = KeyLookup.from_dict(lookup.to_dict())
18
- >>> restored['test'].api_token
19
- 'sk-abcd123'
20
- """
21
-
22
- def to_dict(self):
23
- """
24
- >>> kl = KeyLookup.example()
25
- >>> kl2 = KeyLookup.from_dict(kl.to_dict())
26
- >>> kl2 == kl
27
- True
28
- >>> kl2 is kl
29
- False
30
- """
31
- return {k: asdict(v) for k, v in self.data.items()}
32
-
33
- @classmethod
34
- def from_dict(cls, d):
35
- return cls({k: LanguageModelInput(**v) for k, v in d.items()})
36
-
37
- @classmethod
38
- def example(cls):
39
- return cls(
40
- {
41
- "test": LanguageModelInput.example(),
42
- "openai": LanguageModelInput.example(),
43
- }
44
- )
45
-
46
- def to_dot_env(self):
47
- """Return a string representation of the key lookup collection for a .env file."""
48
- lines = []
49
- for service, lm_input in self.items():
50
- if service != "test":
51
- lines.append(f"EDSL_SERVICE_RPM_{service.upper()}={lm_input.rpm}")
52
- lines.append(f"EDSL_SERVICE_TPM_{service.upper()}={lm_input.tpm}")
53
- key_name = service_to_api_keyname.get(service, service)
54
- lines.append(f"{key_name.upper()}={lm_input.api_token}")
55
- if lm_input.api_id is not None:
56
- lines.append(f"{service.upper()}_API_ID={lm_input.api_id}")
57
- return "\n".join([f"{line}" for line in lines])
58
-
59
-
60
- if __name__ == "__main__":
61
- import doctest
62
-
63
- doctest.testmod(optionflags=doctest.ELLIPSIS)