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
edsl/BaseDiff.py DELETED
@@ -1,260 +0,0 @@
1
- import difflib
2
- import json
3
- from typing import Any, Dict, Tuple
4
- from collections import UserList
5
- import inspect
6
-
7
-
8
- class BaseDiffCollection(UserList):
9
- def __init__(self, diffs=None):
10
- if diffs is None:
11
- diffs = []
12
- super().__init__(diffs)
13
-
14
- def apply(self, obj: Any):
15
- for diff in self:
16
- obj = diff.apply(obj)
17
- return obj
18
-
19
- def add_diff(self, diff) -> "BaseDiffCollection":
20
- self.append(diff)
21
- return self
22
-
23
-
24
- class DummyObject:
25
- def __init__(self, object_dict):
26
- self.object_dict = object_dict
27
-
28
- def to_dict(self):
29
- return self.object_dict
30
-
31
-
32
- class BaseDiff:
33
- def __init__(
34
- self, obj1: Any, obj2: Any, added=None, removed=None, modified=None, level=0
35
- ):
36
- self.level = level
37
-
38
- self.obj1 = obj1
39
- self.obj2 = obj2
40
-
41
- if "sort" in inspect.signature(obj1.to_dict).parameters:
42
- self._dict1 = obj1.to_dict(sort=True)
43
- self._dict2 = obj2.to_dict(sort=True)
44
- else:
45
- self._dict1 = obj1.to_dict()
46
- self._dict2 = obj2.to_dict()
47
- self._obj_class = type(obj1)
48
-
49
- self.added = added
50
- self.removed = removed
51
- self.modified = modified
52
-
53
- def __bool__(self):
54
- return bool(self.added or self.removed or self.modified)
55
-
56
- @property
57
- def added(self):
58
- if self._added is None:
59
- self._added = self._find_added()
60
- return self._added
61
-
62
- def __add__(self, other):
63
- return self.apply(other)
64
-
65
- @added.setter
66
- def added(self, value):
67
- self._added = value if value is not None else self._find_added()
68
-
69
- @property
70
- def removed(self):
71
- if self._removed is None:
72
- self._removed = self._find_removed()
73
- return self._removed
74
-
75
- @removed.setter
76
- def removed(self, value):
77
- self._removed = value if value is not None else self._find_removed()
78
-
79
- @property
80
- def modified(self):
81
- if self._modified is None:
82
- self._modified = self._find_modified()
83
- return self._modified
84
-
85
- @modified.setter
86
- def modified(self, value):
87
- self._modified = value if value is not None else self._find_modified()
88
-
89
- def _find_added(self) -> Dict[Any, Any]:
90
- return {k: self._dict2[k] for k in self._dict2 if k not in self._dict1}
91
-
92
- def _find_removed(self) -> Dict[Any, Any]:
93
- return {k: self._dict1[k] for k in self._dict1 if k not in self._dict2}
94
-
95
- def _find_modified(self) -> Dict[Any, Tuple[Any, Any, str]]:
96
- modified = {}
97
- for k in self._dict1:
98
- if k in self._dict2 and self._dict1[k] != self._dict2[k]:
99
- if isinstance(self._dict1[k], str) and isinstance(self._dict2[k], str):
100
- diff = self._diff_strings(self._dict1[k], self._dict2[k])
101
- modified[k] = (self._dict1[k], self._dict2[k], diff)
102
- elif isinstance(self._dict1[k], dict) and isinstance(
103
- self._dict2[k], dict
104
- ):
105
- diff = self._diff_dicts(self._dict1[k], self._dict2[k])
106
- modified[k] = (self._dict1[k], self._dict2[k], diff)
107
- elif isinstance(self._dict1[k], list) and isinstance(
108
- self._dict2[k], list
109
- ):
110
- d1 = dict(zip(range(len(self._dict1[k])), self._dict1[k]))
111
- d2 = dict(zip(range(len(self._dict2[k])), self._dict2[k]))
112
- diff = BaseDiff(
113
- DummyObject(d1), DummyObject(d2), level=self.level + 1
114
- )
115
- modified[k] = (self._dict1[k], self._dict2[k], diff)
116
- else:
117
- modified[k] = (self._dict1[k], self._dict2[k], "")
118
- return modified
119
-
120
- @staticmethod
121
- def is_json(string_that_could_be_json: str) -> bool:
122
- try:
123
- json.loads(string_that_could_be_json)
124
- return True
125
- except json.JSONDecodeError:
126
- return False
127
-
128
- def _diff_dicts(self, dict1: Dict[str, Any], dict2: Dict[str, Any]) -> str:
129
- diff = BaseDiff(DummyObject(dict1), DummyObject(dict2), level=self.level + 1)
130
- return diff
131
-
132
- def _diff_strings(self, str1: str, str2: str) -> str:
133
- if self.is_json(str1) and self.is_json(str2):
134
- diff = self._diff_dicts(json.loads(str1), json.loads(str2))
135
- return diff
136
- diff = difflib.ndiff(str1.splitlines(), str2.splitlines())
137
- return diff
138
-
139
- def apply(self, obj: Any):
140
- """Apply the diff to the object."""
141
-
142
- new_obj_dict = obj.to_dict()
143
- for k, v in self.added.items():
144
- new_obj_dict[k] = v
145
- for k in self.removed.keys():
146
- del new_obj_dict[k]
147
- for k, (v1, v2, diff) in self.modified.items():
148
- new_obj_dict[k] = v2
149
-
150
- return obj.from_dict(new_obj_dict)
151
-
152
- def to_dict(self) -> Dict[str, Any]:
153
- return {
154
- "added": self.added,
155
- "removed": self.removed,
156
- "modified": self.modified,
157
- "obj1": self._dict1,
158
- "obj2": self._dict2,
159
- "obj_class": self._obj_class.__name__,
160
- "level": self.level,
161
- }
162
-
163
- @classmethod
164
- def from_dict(cls, diff_dict: Dict[str, Any], obj1: Any, obj2: Any):
165
- return cls(
166
- obj1=obj1,
167
- obj2=obj2,
168
- added=diff_dict["added"],
169
- removed=diff_dict["removed"],
170
- modified=diff_dict["modified"],
171
- level=diff_dict["level"],
172
- )
173
-
174
- class Results(UserList):
175
- def __init__(self, prepend=" ", level=0):
176
- super().__init__()
177
- self.prepend = prepend
178
- self.level = level
179
-
180
- def append(self, item):
181
- super().append(self.prepend * self.level + item)
182
-
183
- def __str__(self):
184
- prepend = " "
185
- result = self.Results(level=self.level, prepend="\t")
186
- if self.added:
187
- result.append("Added keys and values:")
188
- for k, v in self.added.items():
189
- result.append(prepend + f" {k}: {v}")
190
- if self.removed:
191
- result.append("Removed keys and values:")
192
- for k, v in self.removed.items():
193
- result.append(f" {k}: {v}")
194
- if self.modified:
195
- result.append("Modified keys and values:")
196
- for k, (v1, v2, diff) in self.modified.items():
197
- result.append(f"Key: {k}:")
198
- result.append(f" Old value: {v1}")
199
- result.append(f" New value: {v2}")
200
- if diff:
201
- result.append(f" Diff:")
202
- try:
203
- for line in diff:
204
- result.append(f" {line}")
205
- except:
206
- result.append(f" {diff}")
207
- return "\n".join(result)
208
-
209
- def __repr__(self):
210
- return (
211
- f"BaseDiff(obj1={self.obj1!r}, obj2={self.obj2!r}, added={self.added!r}, "
212
- f"removed={self.removed!r}, modified={self.modified!r})"
213
- )
214
-
215
- def add_diff(self, diff) -> "BaseDiffCollection":
216
- return BaseDiffCollection([self, diff])
217
-
218
-
219
- if __name__ == "__main__":
220
- from edsl import Question
221
-
222
- q_ft = Question.example("free_text")
223
- q_mc = Question.example("multiple_choice")
224
-
225
- diff1 = q_ft - q_mc
226
- assert q_ft == q_mc + diff1
227
- assert q_ft == diff1.apply(q_mc)
228
- # new_q_mc = diff1.apply(q_ft)
229
- # assert new_q_mc == q_mc
230
-
231
- # new_q_mc = q_ft + diff1
232
- # assert new_q_mc == q_mc
233
-
234
- # new_q_mc = diff1 + q_ft
235
- # assert new_q_mc == q_mc
236
-
237
- # ## Test chain of diffs
238
- q0 = Question.example("free_text")
239
- q1 = q0.copy()
240
- q1.question_text = "Why is Buzzard's Bay so named?"
241
- diff1 = q1 - q0
242
- q2 = q1.copy()
243
- q2.question_name = "buzzard_bay"
244
- diff2 = q2 - q1
245
-
246
- diff_chain = diff1.add_diff(diff2)
247
-
248
- new_q2 = diff_chain.apply(q0)
249
- assert new_q2 == q2
250
-
251
- new_q2 = diff_chain + q0
252
- assert new_q2 == q2
253
-
254
- # new_diffs = diff1.add_diff(diff1).add_diff(diff1)
255
- # assert len(new_diffs) == 3
256
-
257
- # q0 = Question.example("free_text")
258
- # q1 = Question.example("free_text")
259
- # q1.question_text = "Why is Buzzard's Bay so named?"
260
- # q2 = q1.copy()
@@ -1,260 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- import asyncio
3
- from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
4
-
5
- from edsl.utilities.decorators import jupyter_nb_handler
6
- from edsl.data_transfer_models import AgentResponseDict
7
-
8
- if TYPE_CHECKING:
9
- from edsl.prompts.Prompt import Prompt
10
- from edsl.data.Cache import Cache
11
- from edsl.questions.QuestionBase import QuestionBase
12
- from edsl.scenarios.Scenario import Scenario
13
- from edsl.surveys.MemoryPlan import MemoryPlan
14
- from edsl.language_models.LanguageModel import LanguageModel
15
- from edsl.surveys.Survey import Survey
16
- from edsl.agents.Agent import Agent
17
- from edsl.language_models.key_management.KeyLookup import KeyLookup
18
-
19
- from edsl.data_transfer_models import EDSLResultObjectInput
20
- from edsl.agents.PromptConstructor import PromptConstructor
21
- from edsl.agents.prompt_helpers import PromptPlan
22
-
23
-
24
- class InvigilatorBase(ABC):
25
- """An invigiator (someone who administers an exam) is a class that is responsible for administering a question to an agent.
26
-
27
- >>> InvigilatorBase.example().answer_question()
28
- {'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
29
-
30
- >>> InvigilatorBase.example().get_failed_task_result(failure_reason="Failed to get response").comment
31
- 'Failed to get response'
32
-
33
- This returns an empty prompt because there is no memory the agent needs to have at q0.
34
- """
35
-
36
- def __init__(
37
- self,
38
- agent: "Agent",
39
- question: "QuestionBase",
40
- scenario: "Scenario",
41
- model: "LanguageModel",
42
- memory_plan: "MemoryPlan",
43
- current_answers: dict,
44
- survey: Optional["Survey"],
45
- cache: Optional["Cache"] = None,
46
- iteration: Optional[int] = 1,
47
- additional_prompt_data: Optional[dict] = None,
48
- raise_validation_errors: Optional[bool] = True,
49
- prompt_plan: Optional["PromptPlan"] = None,
50
- key_lookup: Optional["KeyLookup"] = None,
51
- ):
52
- """Initialize a new Invigilator."""
53
- self.agent = agent
54
- self.question = question
55
- self.scenario = scenario
56
- self.model = model
57
- self.memory_plan = memory_plan
58
- self.current_answers = current_answers or {}
59
- self.iteration = iteration
60
- self.additional_prompt_data = additional_prompt_data
61
- self.cache = cache
62
- self.survey = survey
63
- self.raise_validation_errors = raise_validation_errors
64
- self.key_lookup = key_lookup
65
-
66
- if prompt_plan is None:
67
- self.prompt_plan = PromptPlan()
68
- else:
69
- self.prompt_plan = prompt_plan
70
-
71
- # placeholder to store the raw model response
72
- self.raw_model_response = None
73
-
74
- @property
75
- def prompt_constructor(self) -> PromptConstructor:
76
- """Return the prompt constructor."""
77
- return PromptConstructor.from_invigilator(self, prompt_plan=self.prompt_plan)
78
-
79
- def to_dict(self, include_cache=False) -> Dict[str, Any]:
80
- attributes = [
81
- "agent",
82
- "question",
83
- "scenario",
84
- "model",
85
- "memory_plan",
86
- "current_answers",
87
- "iteration",
88
- "additional_prompt_data",
89
- "survey",
90
- "raw_model_response",
91
- ]
92
- if include_cache:
93
- attributes.append("cache")
94
-
95
- def serialize_attribute(attr):
96
- value = getattr(self, attr)
97
- if value is None:
98
- return None
99
- if hasattr(value, "to_dict"):
100
- return value.to_dict()
101
- if isinstance(value, (int, float, str, bool, dict, list)):
102
- return value
103
- return str(value)
104
-
105
- return {attr: serialize_attribute(attr) for attr in attributes}
106
-
107
- @classmethod
108
- def from_dict(cls, data) -> "InvigilatorBase":
109
- from edsl.agents.Agent import Agent
110
- from edsl.questions import QuestionBase
111
- from edsl.scenarios.Scenario import Scenario
112
- from edsl.surveys.MemoryPlan import MemoryPlan
113
- from edsl.language_models.LanguageModel import LanguageModel
114
- from edsl.surveys.Survey import Survey
115
- from edsl.data.Cache import Cache
116
-
117
- attributes_to_classes = {
118
- "agent": Agent,
119
- "question": QuestionBase,
120
- "scenario": Scenario,
121
- "model": LanguageModel,
122
- "memory_plan": MemoryPlan,
123
- "survey": Survey,
124
- "cache": Cache,
125
- }
126
- d = {}
127
- for attr, cls_ in attributes_to_classes.items():
128
- if attr in data and data[attr] is not None:
129
- if attr not in data:
130
- d[attr] = {}
131
- else:
132
- d[attr] = cls_.from_dict(data[attr])
133
-
134
- d["current_answers"] = data["current_answers"]
135
- d["iteration"] = data["iteration"]
136
- d["additional_prompt_data"] = data["additional_prompt_data"]
137
-
138
- d = cls(**d)
139
- d.raw_model_response = data.get("raw_model_response")
140
- return d
141
-
142
- def __repr__(self) -> str:
143
- """Return a string representation of the Invigilator.
144
-
145
- >>> InvigilatorBase.example().__repr__()
146
- 'InvigilatorExample(...)'
147
-
148
- """
149
- return f"{self.__class__.__name__}(agent={repr(self.agent)}, question={repr(self.question)}, scenario={repr(self.scenario)}, model={repr(self.model)}, memory_plan={repr(self.memory_plan)}, current_answers={repr(self.current_answers)}, iteration={repr(self.iteration)}, additional_prompt_data={repr(self.additional_prompt_data)}, cache={repr(self.cache)})"
150
-
151
- def get_failed_task_result(self, failure_reason: str) -> EDSLResultObjectInput:
152
- """Return an AgentResponseDict used in case the question-asking fails.
153
-
154
- Possible reasons include:
155
- - Legimately skipped because of skip logic
156
- - Failed to get response from the model
157
-
158
- """
159
- data = {
160
- "answer": None,
161
- "generated_tokens": None,
162
- "comment": failure_reason,
163
- "question_name": self.question.question_name,
164
- "prompts": self.get_prompts(),
165
- "cached_response": None,
166
- "raw_model_response": None,
167
- "cache_used": None,
168
- "cache_key": None,
169
- }
170
- return EDSLResultObjectInput(**data)
171
-
172
- def get_prompts(self) -> Dict[str, "Prompt"]:
173
- """Return the prompt used."""
174
- from edsl.prompts.Prompt import Prompt
175
-
176
- return {
177
- "user_prompt": Prompt("NA"),
178
- "system_prompt": Prompt("NA"),
179
- }
180
-
181
- @abstractmethod
182
- async def async_answer_question(self):
183
- """Asnwer a question."""
184
- pass
185
-
186
- @jupyter_nb_handler
187
- def answer_question(self) -> Coroutine:
188
- """Return a function that gets the answers to the question."""
189
-
190
- async def main():
191
- """Return the answer to the question."""
192
- results = await asyncio.gather(self.async_answer_question())
193
- return results[0] # Since there's only one task, return its result
194
-
195
- return main()
196
-
197
- @classmethod
198
- def example(
199
- cls, throw_an_exception=False, question=None, scenario=None, survey=None
200
- ) -> "InvigilatorBase":
201
- """Return an example invigilator.
202
-
203
- >>> InvigilatorBase.example()
204
- InvigilatorExample(...)
205
-
206
- >>> InvigilatorBase.example().answer_question()
207
- {'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
208
-
209
- >>> InvigilatorBase.example(throw_an_exception=True).answer_question()
210
- Traceback (most recent call last):
211
- ...
212
- Exception: This is a test error
213
- """
214
- from edsl.agents.Agent import Agent
215
- from edsl.scenarios.Scenario import Scenario
216
- from edsl.surveys.MemoryPlan import MemoryPlan
217
- from edsl.language_models.model import Model
218
- from edsl.surveys.Survey import Survey
219
-
220
- model = Model("test", canned_response="SPAM!")
221
-
222
- if throw_an_exception:
223
- model.throw_exception = True
224
- agent = Agent.example()
225
-
226
- if not survey:
227
- survey = Survey.example()
228
-
229
- if question not in survey.questions and question is not None:
230
- survey.add_question(question)
231
-
232
- question = question or survey.questions[0]
233
- scenario = scenario or Scenario.example()
234
- memory_plan = MemoryPlan(survey=survey)
235
- current_answers = None
236
-
237
- class InvigilatorExample(cls):
238
- """An example invigilator."""
239
-
240
- async def async_answer_question(self):
241
- """Answer a question."""
242
- return await self.model.async_execute_model_call(
243
- user_prompt="Hello", system_prompt="Hi"
244
- )
245
-
246
- return InvigilatorExample(
247
- agent=agent,
248
- question=question,
249
- scenario=scenario,
250
- survey=survey,
251
- model=model,
252
- memory_plan=memory_plan,
253
- current_answers=current_answers,
254
- )
255
-
256
-
257
- if __name__ == "__main__":
258
- import doctest
259
-
260
- doctest.testmod(optionflags=doctest.ELLIPSIS)