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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +430 -113
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/dataset/dataset_operations_mixin.py +1492 -0
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +12 -12
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +321 -155
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +20 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +10 -16
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +420 -216
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/file_store.py +755 -0
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +424 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +20 -21
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/METADATA +3 -2
  230. edsl-0.1.48.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -426
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/auto/AutoStudy.py +0 -130
  236. edsl/auto/StageBase.py +0 -243
  237. edsl/auto/StageGenerateSurvey.py +0 -178
  238. edsl/auto/StageLabelQuestions.py +0 -125
  239. edsl/auto/StagePersona.py +0 -61
  240. edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
  241. edsl/auto/StagePersonaDimensionValues.py +0 -74
  242. edsl/auto/StagePersonaDimensions.py +0 -69
  243. edsl/auto/StageQuestions.py +0 -74
  244. edsl/auto/SurveyCreatorPipeline.py +0 -21
  245. edsl/auto/utilities.py +0 -218
  246. edsl/base/Base.py +0 -279
  247. edsl/coop/PriceFetcher.py +0 -54
  248. edsl/data/Cache.py +0 -580
  249. edsl/data/CacheEntry.py +0 -230
  250. edsl/data/SQLiteDict.py +0 -292
  251. edsl/data/__init__.py +0 -5
  252. edsl/data/orm.py +0 -10
  253. edsl/exceptions/cache.py +0 -5
  254. edsl/exceptions/coop.py +0 -14
  255. edsl/exceptions/data.py +0 -14
  256. edsl/exceptions/scenarios.py +0 -29
  257. edsl/jobs/Answers.py +0 -43
  258. edsl/jobs/JobsPrompts.py +0 -354
  259. edsl/jobs/buckets/BucketCollection.py +0 -134
  260. edsl/jobs/buckets/ModelBuckets.py +0 -65
  261. edsl/jobs/buckets/TokenBucket.py +0 -283
  262. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  263. edsl/jobs/interviews/Interview.py +0 -395
  264. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  265. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  266. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  267. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  268. edsl/jobs/tasks/TaskCreators.py +0 -64
  269. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  270. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  271. edsl/language_models/LanguageModel.py +0 -635
  272. edsl/language_models/ServiceDataSources.py +0 -0
  273. edsl/language_models/key_management/KeyLookup.py +0 -63
  274. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  275. edsl/language_models/key_management/models.py +0 -137
  276. edsl/questions/QuestionBase.py +0 -539
  277. edsl/questions/QuestionFreeText.py +0 -130
  278. edsl/questions/derived/QuestionLikertFive.py +0 -76
  279. edsl/results/DatasetExportMixin.py +0 -911
  280. edsl/results/ResultsExportMixin.py +0 -45
  281. edsl/results/TextEditor.py +0 -50
  282. edsl/results/results_fetch_mixin.py +0 -33
  283. edsl/results/results_tools_mixin.py +0 -98
  284. edsl/scenarios/DocumentChunker.py +0 -104
  285. edsl/scenarios/FileStore.py +0 -564
  286. edsl/scenarios/Scenario.py +0 -548
  287. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  288. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  289. edsl/scenarios/handlers/latex.py +0 -5
  290. edsl/shared.py +0 -1
  291. edsl/surveys/Survey.py +0 -1306
  292. edsl/surveys/SurveyQualtricsImport.py +0 -284
  293. edsl/surveys/SurveyToApp.py +0 -141
  294. edsl/surveys/instructions/__init__.py +0 -0
  295. edsl/tools/__init__.py +0 -1
  296. edsl/tools/clusters.py +0 -192
  297. edsl/tools/embeddings.py +0 -27
  298. edsl/tools/embeddings_plotting.py +0 -118
  299. edsl/tools/plotting.py +0 -112
  300. edsl/tools/summarize.py +0 -18
  301. edsl/utilities/data/Registry.py +0 -6
  302. edsl/utilities/data/__init__.py +0 -1
  303. edsl/utilities/data/scooter_results.json +0 -1
  304. edsl-0.1.46.dist-info/RECORD +0 -366
  305. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  306. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  307. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  308. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  309. /edsl/{results → dataset/display}/table_display.css +0 -0
  310. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  311. /edsl/{results → dataset}/tree_explore.py +0 -0
  312. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  313. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  314. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  315. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  316. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  317. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  318. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  319. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  320. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  321. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  322. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  323. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  324. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  325. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  326. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  327. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
  328. {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -0,0 +1,477 @@
1
+ from abc import ABC, abstractmethod
2
+ import asyncio
3
+ from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
4
+
5
+ from ..utilities.decorators import jupyter_nb_handler
6
+ from ..data_transfer_models import AgentResponseDict
7
+
8
+ if TYPE_CHECKING:
9
+ from ..prompts import Prompt
10
+ from ..caching import Cache
11
+ from ..questions import QuestionBase
12
+ from ..scenarios import Scenario
13
+ from ..surveys.memory import MemoryPlan
14
+ from ..language_models import LanguageModel
15
+ from ..surveys import Survey
16
+ from ..agents import Agent
17
+ from ..key_management import KeyLookup
18
+
19
+ from ..data_transfer_models import EDSLResultObjectInput
20
+ from .prompt_constructor import PromptConstructor
21
+ from .prompt_helpers import PromptPlan
22
+
23
+
24
+ class InvigilatorBase(ABC):
25
+ """
26
+ Abstract base class for invigilators that administer questions to agents.
27
+
28
+ An invigilator is responsible for the entire process of administering a question
29
+ to an agent, including:
30
+ 1. Constructing appropriate prompts based on the question, scenario, and agent
31
+ 2. Handling agent-specific presentation of the question
32
+ 3. Processing model responses and validating them
33
+ 4. Managing memory and state across sequential questions
34
+ 5. Handling errors and validation failures
35
+
36
+ This abstract base class defines the interface that all invigilators must implement
37
+ and provides common functionality. Concrete subclasses implement the abstract
38
+ async_answer_question method to define the specific question-answering behavior.
39
+
40
+ Technical architecture:
41
+ - Uses async/await pattern for efficient concurrent processing
42
+ - Maintains references to agent, question, scenario, model and other components
43
+ - Delegates prompt construction to the PromptConstructor class
44
+ - Manages caching and memory for efficient execution
45
+
46
+ Examples:
47
+ >>> # Example usage, returning a test response
48
+ >>> InvigilatorBase.example().answer_question()
49
+ {'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
50
+
51
+ >>> # Example of error handling
52
+ >>> InvigilatorBase.example().get_failed_task_result(failure_reason="Failed to get response").comment
53
+ 'Failed to get response'
54
+
55
+ Implementation note:
56
+ Concrete invigilator classes must implement the async_answer_question method,
57
+ which performs the actual question administration and returns the result.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ agent: "Agent",
63
+ question: "QuestionBase",
64
+ scenario: "Scenario",
65
+ model: "LanguageModel",
66
+ memory_plan: "MemoryPlan",
67
+ current_answers: dict,
68
+ survey: Optional["Survey"],
69
+ cache: Optional["Cache"] = None,
70
+ iteration: Optional[int] = 1,
71
+ additional_prompt_data: Optional[dict] = None,
72
+ raise_validation_errors: Optional[bool] = True,
73
+ prompt_plan: Optional["PromptPlan"] = None,
74
+ key_lookup: Optional["KeyLookup"] = None,
75
+ ):
76
+ """
77
+ Initialize a new Invigilator.
78
+
79
+ This constructor sets up an invigilator with all the components required to
80
+ administer a question to an agent. It establishes references to the agent,
81
+ question, scenario, language model, and other components that participate in
82
+ the question-asking process.
83
+
84
+ Args:
85
+ agent: The agent to which the question will be administered.
86
+ question: The question to be asked.
87
+ scenario: The scenario providing context for the question.
88
+ model: The language model to use for generating responses (for AI agents).
89
+ memory_plan: Plan for managing memory across questions in a survey.
90
+ current_answers: Dictionary of answers to previous questions.
91
+ survey: Optional reference to the parent survey.
92
+ cache: Optional cache for storing and retrieving responses.
93
+ iteration: Counter for tracking question repetitions (for retry logic).
94
+ additional_prompt_data: Additional data to include in prompts.
95
+ raise_validation_errors: Whether to raise exceptions on validation errors.
96
+ prompt_plan: Custom prompt plan to use instead of the default.
97
+ key_lookup: Optional key lookup for API key management.
98
+
99
+ Technical Notes:
100
+ - The current_answers dict allows for referencing previous answers in prompts
101
+ - The memory_plan controls what information is carried forward between questions
102
+ - The prompt_plan configures the structure of system and user prompts
103
+ - The raw_model_response field stores the unprocessed response from the model
104
+ """
105
+ self.agent = agent
106
+ self.question = question
107
+ self.scenario = scenario
108
+ self.model = model
109
+ self.memory_plan = memory_plan
110
+ self.current_answers = current_answers or {}
111
+ self.iteration = iteration
112
+ self.additional_prompt_data = additional_prompt_data
113
+ self.cache = cache
114
+ self.survey = survey
115
+ self.raise_validation_errors = raise_validation_errors
116
+ self.key_lookup = key_lookup
117
+
118
+ # Initialize prompt plan (default or custom)
119
+ if prompt_plan is None:
120
+ self.prompt_plan = PromptPlan()
121
+ else:
122
+ self.prompt_plan = prompt_plan
123
+
124
+ # Storage for the raw model response
125
+ self.raw_model_response = None
126
+
127
+ @property
128
+ def prompt_constructor(self) -> PromptConstructor:
129
+ """
130
+ Get the prompt constructor for this invigilator.
131
+
132
+ The prompt constructor is responsible for generating the prompts that will be
133
+ sent to the language model based on the question, scenario, agent, and other
134
+ context. This property lazily creates and returns a PromptConstructor instance
135
+ configured for this invigilator.
136
+
137
+ Returns:
138
+ PromptConstructor: A prompt constructor instance configured for this invigilator.
139
+
140
+ Technical Notes:
141
+ - This uses the factory method from_invigilator on PromptConstructor
142
+ - The constructor has access to all the context of this invigilator
143
+ - The prompt_plan controls the structure and components of the prompts
144
+ - This is a key part of the separation of concerns in the invigilator architecture
145
+ """
146
+ return PromptConstructor.from_invigilator(self, prompt_plan=self.prompt_plan)
147
+
148
+ def to_dict(self, include_cache: bool = False) -> Dict[str, Any]:
149
+ """
150
+ Serialize the invigilator to a dictionary.
151
+
152
+ This method serializes the invigilator and all its attributes to a dictionary
153
+ representation that can be stored, transmitted, or used to recreate an equivalent
154
+ invigilator. It handles complex nested objects by recursively serializing them
155
+ if they have a to_dict method.
156
+
157
+ Args:
158
+ include_cache: Whether to include the cache in the serialized output.
159
+ Defaults to False as the cache can be very large.
160
+
161
+ Returns:
162
+ A dictionary representation of the invigilator.
163
+
164
+ Technical Notes:
165
+ - Objects with a to_dict method are serialized recursively
166
+ - Primitive types (int, float, str, bool, dict, list) are included directly
167
+ - Other objects are converted to strings
168
+ - The cache is optionally included based on the include_cache parameter
169
+ """
170
+ attributes = [
171
+ "agent",
172
+ "question",
173
+ "scenario",
174
+ "model",
175
+ "memory_plan",
176
+ "current_answers",
177
+ "iteration",
178
+ "additional_prompt_data",
179
+ "survey",
180
+ "raw_model_response",
181
+ ]
182
+ if include_cache:
183
+ attributes.append("cache")
184
+
185
+ def serialize_attribute(attr):
186
+ """Helper function to serialize a single attribute."""
187
+ value = getattr(self, attr)
188
+ if value is None:
189
+ return None
190
+ if hasattr(value, "to_dict"):
191
+ return value.to_dict()
192
+ if isinstance(value, (int, float, str, bool, dict, list)):
193
+ return value
194
+ return str(value)
195
+
196
+ return {attr: serialize_attribute(attr) for attr in attributes}
197
+
198
+ @classmethod
199
+ def from_dict(cls, data: Dict[str, Any]) -> "InvigilatorBase":
200
+ """
201
+ Create an invigilator from a dictionary representation.
202
+
203
+ This class method deserializes an invigilator from a dictionary, typically
204
+ one created by the to_dict method. It recursively deserializes nested objects
205
+ using their from_dict methods.
206
+
207
+ Args:
208
+ data: Dictionary representation of an invigilator.
209
+
210
+ Returns:
211
+ An invigilator instance reconstructed from the dictionary.
212
+
213
+ Technical Notes:
214
+ - This method implements the inverse of to_dict
215
+ - Complex objects are reconstructed using their respective from_dict methods
216
+ - The mapping between attribute names and classes is defined in attributes_to_classes
217
+ - Special handling is provided for raw_model_response which is set after construction
218
+ - This method supports the persistence and restoration of invigilator state
219
+ """
220
+ from ..agents import Agent
221
+ from ..questions import QuestionBase
222
+ from ..scenarios import Scenario
223
+ from ..surveys.memory import MemoryPlan
224
+ from ..language_models import LanguageModel
225
+ from ..surveys import Survey
226
+ from ..data import Cache
227
+
228
+ # Map attribute names to their corresponding classes
229
+ attributes_to_classes = {
230
+ "agent": Agent,
231
+ "question": QuestionBase,
232
+ "scenario": Scenario,
233
+ "model": LanguageModel,
234
+ "memory_plan": MemoryPlan,
235
+ "survey": Survey,
236
+ "cache": Cache,
237
+ }
238
+
239
+ # Reconstruct complex objects
240
+ d = {}
241
+ for attr, cls_ in attributes_to_classes.items():
242
+ if attr in data and data[attr] is not None:
243
+ if attr not in data:
244
+ d[attr] = {}
245
+ else:
246
+ d[attr] = cls_.from_dict(data[attr])
247
+
248
+ # Copy primitive data directly
249
+ d["current_answers"] = data["current_answers"]
250
+ d["iteration"] = data["iteration"]
251
+ d["additional_prompt_data"] = data["additional_prompt_data"]
252
+
253
+ # Create the invigilator instance
254
+ invigilator = cls(**d)
255
+
256
+ # Set raw_model_response after construction
257
+ invigilator.raw_model_response = data.get("raw_model_response")
258
+ return invigilator
259
+
260
+ def __repr__(self) -> str:
261
+ """
262
+ Get a string representation of the Invigilator.
263
+
264
+ This method creates a detailed string representation of the invigilator
265
+ including all its major components. This is useful for debugging and logging.
266
+
267
+ Returns:
268
+ A string representation of the invigilator.
269
+
270
+ Examples:
271
+ >>> InvigilatorBase.example().__repr__()
272
+ 'InvigilatorExample(...)'
273
+ """
274
+ 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)})"
275
+
276
+ def get_failed_task_result(self, failure_reason: str) -> EDSLResultObjectInput:
277
+ """
278
+ Create a result object for a failed question-answering task.
279
+
280
+ This method constructs a standardized result object that represents a failed
281
+ attempt to answer a question. The result includes the failure reason and
282
+ context information, allowing for consistent handling of failures throughout
283
+ the system.
284
+
285
+ Args:
286
+ failure_reason: Description of why the question-answering task failed.
287
+
288
+ Returns:
289
+ An EDSLResultObjectInput representing the failed task.
290
+
291
+ Technical Notes:
292
+ - Used for both expected failures (e.g., skip logic) and unexpected errors
293
+ - Maintains a consistent structure for all results, regardless of success or failure
294
+ - Includes the question name and prompts for context and debugging
295
+ - Sets all response-related fields to None to indicate no response was obtained
296
+ """
297
+ data = {
298
+ "answer": None,
299
+ "generated_tokens": None,
300
+ "comment": failure_reason,
301
+ "question_name": self.question.question_name,
302
+ "prompts": self.get_prompts(),
303
+ "cached_response": None,
304
+ "raw_model_response": None,
305
+ "cache_used": None,
306
+ "cache_key": None,
307
+ }
308
+ return EDSLResultObjectInput(**data)
309
+
310
+ def get_prompts(self) -> Dict[str, "Prompt"]:
311
+ """
312
+ Get the prompts used by this invigilator.
313
+
314
+ This base implementation returns placeholder prompts. Subclasses should
315
+ override this method to provide the actual prompts used in question answering.
316
+
317
+ Returns:
318
+ A dictionary mapping prompt types to Prompt objects.
319
+
320
+ Technical Notes:
321
+ - This is a fallback implementation that returns "NA" prompts
322
+ - Concrete invigilator implementations generate real prompts using the prompt_constructor
323
+ - The returned dictionary uses standardized keys like "user_prompt" and "system_prompt"
324
+ """
325
+ from ..prompts import Prompt
326
+
327
+ return {
328
+ "user_prompt": Prompt("NA"),
329
+ "system_prompt": Prompt("NA"),
330
+ }
331
+
332
+ @abstractmethod
333
+ async def async_answer_question(self) -> Any:
334
+ """
335
+ Asynchronously administer a question to an agent and get the response.
336
+
337
+ This abstract method must be implemented by concrete invigilator subclasses.
338
+ It should handle the entire process of administering a question to an agent
339
+ and processing the response.
340
+
341
+ Returns:
342
+ The processed response from the agent.
343
+
344
+ Technical Notes:
345
+ - This is an async method to support efficient concurrent execution
346
+ - Different invigilator types implement this differently:
347
+ - InvigilatorAI: Sends prompts to a language model
348
+ - InvigilatorHuman: Displays questions to a human user
349
+ - InvigilatorFunctional: Executes a function to generate responses
350
+ """
351
+ pass
352
+
353
+ @jupyter_nb_handler
354
+ def answer_question(self) -> Coroutine:
355
+ """
356
+ Get the answer to the question from the agent.
357
+
358
+ This method creates and returns a coroutine that, when awaited, will
359
+ administer the question to the agent and return the response. It
360
+ handles the async execution details and provides a convenient interface
361
+ for both async and sync contexts.
362
+
363
+ Returns:
364
+ A coroutine that, when awaited, returns the agent's response.
365
+
366
+ Technical Notes:
367
+ - The @jupyter_nb_handler decorator enables this to work properly in Jupyter notebooks
368
+ - This method uses asyncio.gather to await the async_answer_question method
369
+ - It acts as a bridge between the async and sync parts of the system
370
+ - In synchronous contexts, this method can be called directly to get the response
371
+ """
372
+ async def main():
373
+ """Execute the question-answering process and return the result."""
374
+ results = await asyncio.gather(self.async_answer_question())
375
+ return results[0] # Since there's only one task, return its result
376
+
377
+ return main()
378
+
379
+ @classmethod
380
+ def example(
381
+ cls,
382
+ throw_an_exception: bool = False,
383
+ question: Optional["QuestionBase"] = None,
384
+ scenario: Optional["Scenario"] = None,
385
+ survey: Optional["Survey"] = None
386
+ ) -> "InvigilatorBase":
387
+ """
388
+ Create an example invigilator for testing and documentation.
389
+
390
+ This factory method creates a concrete implementation of the InvigilatorBase
391
+ with predefined components suitable for testing, examples, and documentation.
392
+ It supports customization through parameters and can be configured to simulate
393
+ error conditions.
394
+
395
+ Args:
396
+ throw_an_exception: If True, the model will raise an exception when called.
397
+ question: Custom question to use instead of the default.
398
+ scenario: Custom scenario to use instead of the default.
399
+ survey: Custom survey to use instead of the default.
400
+
401
+ Returns:
402
+ A concrete InvigilatorBase instance ready for testing.
403
+
404
+ Examples:
405
+ >>> # Basic example with default components
406
+ >>> InvigilatorBase.example()
407
+ InvigilatorExample(...)
408
+
409
+ >>> # Example answering a question with a canned response
410
+ >>> InvigilatorBase.example().answer_question()
411
+ {'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
412
+
413
+ >>> # Example that simulates an error condition
414
+ >>> InvigilatorBase.example(throw_an_exception=True).answer_question()
415
+ Traceback (most recent call last):
416
+ ...
417
+ Exception: This is a test error
418
+
419
+ Technical Notes:
420
+ - Creates an anonymous subclass (InvigilatorExample) with a simplified implementation
421
+ - Uses example() factory methods on other classes to create compatible components
422
+ - The canned_response in the model provides predictable output for testing
423
+ - The throw_exception parameter can be used to test error handling
424
+ """
425
+ from ..agents import Agent
426
+ from ..scenarios import Scenario
427
+ from ..surveys.memory import MemoryPlan
428
+ from ..language_models import Model
429
+ from ..surveys import Survey
430
+
431
+ # Create a test model with predictable output
432
+ model = Model("test", canned_response="SPAM!")
433
+
434
+ # Configure the model to throw an exception if requested
435
+ if throw_an_exception:
436
+ model.throw_exception = True
437
+
438
+ # Create or use the provided components
439
+ agent = Agent.example()
440
+ survey = survey or Survey.example()
441
+
442
+ # Ensure the question is in the survey
443
+ if question is not None and question not in survey.questions:
444
+ survey.add_question(question)
445
+
446
+ # Get or create the remaining required components
447
+ question = question or survey.questions[0]
448
+ scenario = scenario or Scenario.example()
449
+ memory_plan = MemoryPlan(survey=survey)
450
+ current_answers = None
451
+
452
+ # Define a concrete implementation of the abstract class
453
+ class InvigilatorExample(cls):
454
+ """An example invigilator implementation for testing."""
455
+
456
+ async def async_answer_question(self):
457
+ """Implement the abstract method with simplified behavior."""
458
+ return await self.model.async_execute_model_call(
459
+ user_prompt="Hello", system_prompt="Hi"
460
+ )
461
+
462
+ # Create and return the example invigilator
463
+ return InvigilatorExample(
464
+ agent=agent,
465
+ question=question,
466
+ scenario=scenario,
467
+ survey=survey,
468
+ model=model,
469
+ memory_plan=memory_plan,
470
+ current_answers=current_answers,
471
+ )
472
+
473
+
474
+ if __name__ == "__main__":
475
+ import doctest
476
+
477
+ doctest.testmod(optionflags=doctest.ELLIPSIS)