edsl 0.1.15__py3-none-any.whl → 0.1.40__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 (407) hide show
  1. edsl/Base.py +348 -38
  2. edsl/BaseDiff.py +260 -0
  3. edsl/TemplateLoader.py +24 -0
  4. edsl/__init__.py +45 -10
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +842 -144
  7. edsl/agents/AgentList.py +521 -25
  8. edsl/agents/Invigilator.py +250 -374
  9. edsl/agents/InvigilatorBase.py +257 -0
  10. edsl/agents/PromptConstructor.py +272 -0
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/descriptors.py +43 -13
  14. edsl/agents/prompt_helpers.py +129 -0
  15. edsl/agents/question_option_processor.py +172 -0
  16. edsl/auto/AutoStudy.py +130 -0
  17. edsl/auto/StageBase.py +243 -0
  18. edsl/auto/StageGenerateSurvey.py +178 -0
  19. edsl/auto/StageLabelQuestions.py +125 -0
  20. edsl/auto/StagePersona.py +61 -0
  21. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  22. edsl/auto/StagePersonaDimensionValues.py +74 -0
  23. edsl/auto/StagePersonaDimensions.py +69 -0
  24. edsl/auto/StageQuestions.py +74 -0
  25. edsl/auto/SurveyCreatorPipeline.py +21 -0
  26. edsl/auto/utilities.py +218 -0
  27. edsl/base/Base.py +279 -0
  28. edsl/config.py +115 -113
  29. edsl/conversation/Conversation.py +290 -0
  30. edsl/conversation/car_buying.py +59 -0
  31. edsl/conversation/chips.py +95 -0
  32. edsl/conversation/mug_negotiation.py +81 -0
  33. edsl/conversation/next_speaker_utilities.py +93 -0
  34. edsl/coop/CoopFunctionsMixin.py +15 -0
  35. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  36. edsl/coop/PriceFetcher.py +54 -0
  37. edsl/coop/__init__.py +1 -0
  38. edsl/coop/coop.py +1029 -134
  39. edsl/coop/utils.py +131 -0
  40. edsl/data/Cache.py +560 -89
  41. edsl/data/CacheEntry.py +230 -0
  42. edsl/data/CacheHandler.py +168 -0
  43. edsl/data/RemoteCacheSync.py +186 -0
  44. edsl/data/SQLiteDict.py +292 -0
  45. edsl/data/__init__.py +5 -3
  46. edsl/data/orm.py +6 -33
  47. edsl/data_transfer_models.py +74 -27
  48. edsl/enums.py +165 -8
  49. edsl/exceptions/BaseException.py +21 -0
  50. edsl/exceptions/__init__.py +52 -46
  51. edsl/exceptions/agents.py +33 -15
  52. edsl/exceptions/cache.py +5 -0
  53. edsl/exceptions/coop.py +8 -0
  54. edsl/exceptions/general.py +34 -0
  55. edsl/exceptions/inference_services.py +5 -0
  56. edsl/exceptions/jobs.py +15 -0
  57. edsl/exceptions/language_models.py +46 -1
  58. edsl/exceptions/questions.py +80 -5
  59. edsl/exceptions/results.py +16 -5
  60. edsl/exceptions/scenarios.py +29 -0
  61. edsl/exceptions/surveys.py +13 -10
  62. edsl/inference_services/AnthropicService.py +106 -0
  63. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  64. edsl/inference_services/AvailableModelFetcher.py +215 -0
  65. edsl/inference_services/AwsBedrock.py +118 -0
  66. edsl/inference_services/AzureAI.py +215 -0
  67. edsl/inference_services/DeepInfraService.py +18 -0
  68. edsl/inference_services/GoogleService.py +143 -0
  69. edsl/inference_services/GroqService.py +20 -0
  70. edsl/inference_services/InferenceServiceABC.py +80 -0
  71. edsl/inference_services/InferenceServicesCollection.py +138 -0
  72. edsl/inference_services/MistralAIService.py +120 -0
  73. edsl/inference_services/OllamaService.py +18 -0
  74. edsl/inference_services/OpenAIService.py +236 -0
  75. edsl/inference_services/PerplexityService.py +160 -0
  76. edsl/inference_services/ServiceAvailability.py +135 -0
  77. edsl/inference_services/TestService.py +90 -0
  78. edsl/inference_services/TogetherAIService.py +172 -0
  79. edsl/inference_services/data_structures.py +134 -0
  80. edsl/inference_services/models_available_cache.py +118 -0
  81. edsl/inference_services/rate_limits_cache.py +25 -0
  82. edsl/inference_services/registry.py +41 -0
  83. edsl/inference_services/write_available.py +10 -0
  84. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  85. edsl/jobs/Answers.py +21 -20
  86. edsl/jobs/FetchInvigilator.py +47 -0
  87. edsl/jobs/InterviewTaskManager.py +98 -0
  88. edsl/jobs/InterviewsConstructor.py +50 -0
  89. edsl/jobs/Jobs.py +684 -206
  90. edsl/jobs/JobsChecks.py +172 -0
  91. edsl/jobs/JobsComponentConstructor.py +189 -0
  92. edsl/jobs/JobsPrompts.py +270 -0
  93. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  94. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  95. edsl/jobs/RequestTokenEstimator.py +30 -0
  96. edsl/jobs/async_interview_runner.py +138 -0
  97. edsl/jobs/buckets/BucketCollection.py +104 -0
  98. edsl/jobs/buckets/ModelBuckets.py +65 -0
  99. edsl/jobs/buckets/TokenBucket.py +283 -0
  100. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  101. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  102. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  103. edsl/jobs/data_structures.py +120 -0
  104. edsl/jobs/decorators.py +35 -0
  105. edsl/jobs/interviews/Interview.py +392 -0
  106. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
  107. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
  108. edsl/jobs/interviews/InterviewStatistic.py +63 -0
  109. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
  110. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
  111. edsl/jobs/interviews/InterviewStatusLog.py +92 -0
  112. edsl/jobs/interviews/ReportErrors.py +66 -0
  113. edsl/jobs/interviews/interview_status_enum.py +9 -0
  114. edsl/jobs/jobs_status_enums.py +9 -0
  115. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  116. edsl/jobs/results_exceptions_handler.py +98 -0
  117. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
  118. edsl/jobs/runners/JobsRunnerStatus.py +298 -0
  119. edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
  120. edsl/jobs/tasks/TaskCreators.py +64 -0
  121. edsl/jobs/tasks/TaskHistory.py +470 -0
  122. edsl/jobs/tasks/TaskStatusLog.py +23 -0
  123. edsl/jobs/tasks/task_status_enum.py +161 -0
  124. edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
  125. edsl/jobs/tokens/TokenUsage.py +34 -0
  126. edsl/language_models/ComputeCost.py +63 -0
  127. edsl/language_models/LanguageModel.py +507 -386
  128. edsl/language_models/ModelList.py +164 -0
  129. edsl/language_models/PriceManager.py +127 -0
  130. edsl/language_models/RawResponseHandler.py +106 -0
  131. edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
  132. edsl/language_models/__init__.py +1 -8
  133. edsl/language_models/fake_openai_call.py +15 -0
  134. edsl/language_models/fake_openai_service.py +61 -0
  135. edsl/language_models/key_management/KeyLookup.py +63 -0
  136. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  137. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  138. edsl/language_models/key_management/__init__.py +0 -0
  139. edsl/language_models/key_management/models.py +131 -0
  140. edsl/language_models/model.py +256 -0
  141. edsl/language_models/repair.py +109 -41
  142. edsl/language_models/utilities.py +65 -0
  143. edsl/notebooks/Notebook.py +263 -0
  144. edsl/notebooks/NotebookToLaTeX.py +142 -0
  145. edsl/notebooks/__init__.py +1 -0
  146. edsl/prompts/Prompt.py +222 -93
  147. edsl/prompts/__init__.py +1 -1
  148. edsl/questions/ExceptionExplainer.py +77 -0
  149. edsl/questions/HTMLQuestion.py +103 -0
  150. edsl/questions/QuestionBase.py +518 -0
  151. edsl/questions/QuestionBasePromptsMixin.py +221 -0
  152. edsl/questions/QuestionBudget.py +164 -67
  153. edsl/questions/QuestionCheckBox.py +281 -62
  154. edsl/questions/QuestionDict.py +343 -0
  155. edsl/questions/QuestionExtract.py +136 -50
  156. edsl/questions/QuestionFreeText.py +79 -55
  157. edsl/questions/QuestionFunctional.py +138 -41
  158. edsl/questions/QuestionList.py +184 -57
  159. edsl/questions/QuestionMatrix.py +265 -0
  160. edsl/questions/QuestionMultipleChoice.py +293 -69
  161. edsl/questions/QuestionNumerical.py +109 -56
  162. edsl/questions/QuestionRank.py +244 -49
  163. edsl/questions/Quick.py +41 -0
  164. edsl/questions/SimpleAskMixin.py +74 -0
  165. edsl/questions/__init__.py +9 -6
  166. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
  167. edsl/questions/compose_questions.py +13 -7
  168. edsl/questions/data_structures.py +20 -0
  169. edsl/questions/decorators.py +21 -0
  170. edsl/questions/derived/QuestionLikertFive.py +28 -26
  171. edsl/questions/derived/QuestionLinearScale.py +41 -28
  172. edsl/questions/derived/QuestionTopK.py +34 -26
  173. edsl/questions/derived/QuestionYesNo.py +40 -27
  174. edsl/questions/descriptors.py +228 -74
  175. edsl/questions/loop_processor.py +149 -0
  176. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  177. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  178. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  179. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  180. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  181. edsl/questions/prompt_templates/question_list.jinja +17 -0
  182. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  183. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  184. edsl/questions/question_base_gen_mixin.py +168 -0
  185. edsl/questions/question_registry.py +130 -46
  186. edsl/questions/register_questions_meta.py +71 -0
  187. edsl/questions/response_validator_abc.py +188 -0
  188. edsl/questions/response_validator_factory.py +34 -0
  189. edsl/questions/settings.py +5 -2
  190. edsl/questions/templates/__init__.py +0 -0
  191. edsl/questions/templates/budget/__init__.py +0 -0
  192. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  193. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  194. edsl/questions/templates/checkbox/__init__.py +0 -0
  195. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  196. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  197. edsl/questions/templates/dict/__init__.py +0 -0
  198. edsl/questions/templates/dict/answering_instructions.jinja +21 -0
  199. edsl/questions/templates/dict/question_presentation.jinja +1 -0
  200. edsl/questions/templates/extract/__init__.py +0 -0
  201. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  202. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  203. edsl/questions/templates/free_text/__init__.py +0 -0
  204. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  205. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  206. edsl/questions/templates/likert_five/__init__.py +0 -0
  207. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  208. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  209. edsl/questions/templates/linear_scale/__init__.py +0 -0
  210. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  211. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  212. edsl/questions/templates/list/__init__.py +0 -0
  213. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  214. edsl/questions/templates/list/question_presentation.jinja +5 -0
  215. edsl/questions/templates/matrix/__init__.py +1 -0
  216. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  217. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  218. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  219. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  220. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  221. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  222. edsl/questions/templates/numerical/__init__.py +0 -0
  223. edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
  224. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  225. edsl/questions/templates/rank/__init__.py +0 -0
  226. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  227. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  228. edsl/questions/templates/top_k/__init__.py +0 -0
  229. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  230. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  231. edsl/questions/templates/yes_no/__init__.py +0 -0
  232. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  233. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  234. edsl/results/CSSParameterizer.py +108 -0
  235. edsl/results/Dataset.py +550 -19
  236. edsl/results/DatasetExportMixin.py +594 -0
  237. edsl/results/DatasetTree.py +295 -0
  238. edsl/results/MarkdownToDocx.py +122 -0
  239. edsl/results/MarkdownToPDF.py +111 -0
  240. edsl/results/Result.py +477 -173
  241. edsl/results/Results.py +987 -269
  242. edsl/results/ResultsExportMixin.py +28 -125
  243. edsl/results/ResultsGGMixin.py +83 -15
  244. edsl/results/TableDisplay.py +125 -0
  245. edsl/results/TextEditor.py +50 -0
  246. edsl/results/__init__.py +1 -1
  247. edsl/results/file_exports.py +252 -0
  248. edsl/results/results_fetch_mixin.py +33 -0
  249. edsl/results/results_selector.py +145 -0
  250. edsl/results/results_tools_mixin.py +98 -0
  251. edsl/results/smart_objects.py +96 -0
  252. edsl/results/table_data_class.py +12 -0
  253. edsl/results/table_display.css +78 -0
  254. edsl/results/table_renderers.py +118 -0
  255. edsl/results/tree_explore.py +115 -0
  256. edsl/scenarios/ConstructDownloadLink.py +109 -0
  257. edsl/scenarios/DocumentChunker.py +102 -0
  258. edsl/scenarios/DocxScenario.py +16 -0
  259. edsl/scenarios/FileStore.py +543 -0
  260. edsl/scenarios/PdfExtractor.py +40 -0
  261. edsl/scenarios/Scenario.py +431 -62
  262. edsl/scenarios/ScenarioHtmlMixin.py +65 -0
  263. edsl/scenarios/ScenarioList.py +1415 -45
  264. edsl/scenarios/ScenarioListExportMixin.py +45 -0
  265. edsl/scenarios/ScenarioListPdfMixin.py +239 -0
  266. edsl/scenarios/__init__.py +2 -0
  267. edsl/scenarios/directory_scanner.py +96 -0
  268. edsl/scenarios/file_methods.py +85 -0
  269. edsl/scenarios/handlers/__init__.py +13 -0
  270. edsl/scenarios/handlers/csv.py +49 -0
  271. edsl/scenarios/handlers/docx.py +76 -0
  272. edsl/scenarios/handlers/html.py +37 -0
  273. edsl/scenarios/handlers/json.py +111 -0
  274. edsl/scenarios/handlers/latex.py +5 -0
  275. edsl/scenarios/handlers/md.py +51 -0
  276. edsl/scenarios/handlers/pdf.py +68 -0
  277. edsl/scenarios/handlers/png.py +39 -0
  278. edsl/scenarios/handlers/pptx.py +105 -0
  279. edsl/scenarios/handlers/py.py +294 -0
  280. edsl/scenarios/handlers/sql.py +313 -0
  281. edsl/scenarios/handlers/sqlite.py +149 -0
  282. edsl/scenarios/handlers/txt.py +33 -0
  283. edsl/scenarios/scenario_join.py +131 -0
  284. edsl/scenarios/scenario_selector.py +156 -0
  285. edsl/shared.py +1 -0
  286. edsl/study/ObjectEntry.py +173 -0
  287. edsl/study/ProofOfWork.py +113 -0
  288. edsl/study/SnapShot.py +80 -0
  289. edsl/study/Study.py +521 -0
  290. edsl/study/__init__.py +4 -0
  291. edsl/surveys/ConstructDAG.py +92 -0
  292. edsl/surveys/DAG.py +92 -11
  293. edsl/surveys/EditSurvey.py +221 -0
  294. edsl/surveys/InstructionHandler.py +100 -0
  295. edsl/surveys/Memory.py +9 -4
  296. edsl/surveys/MemoryManagement.py +72 -0
  297. edsl/surveys/MemoryPlan.py +156 -35
  298. edsl/surveys/Rule.py +221 -74
  299. edsl/surveys/RuleCollection.py +241 -61
  300. edsl/surveys/RuleManager.py +172 -0
  301. edsl/surveys/Simulator.py +75 -0
  302. edsl/surveys/Survey.py +1079 -339
  303. edsl/surveys/SurveyCSS.py +273 -0
  304. edsl/surveys/SurveyExportMixin.py +235 -40
  305. edsl/surveys/SurveyFlowVisualization.py +181 -0
  306. edsl/surveys/SurveyQualtricsImport.py +284 -0
  307. edsl/surveys/SurveyToApp.py +141 -0
  308. edsl/surveys/__init__.py +4 -2
  309. edsl/surveys/base.py +19 -3
  310. edsl/surveys/descriptors.py +17 -6
  311. edsl/surveys/instructions/ChangeInstruction.py +48 -0
  312. edsl/surveys/instructions/Instruction.py +56 -0
  313. edsl/surveys/instructions/InstructionCollection.py +82 -0
  314. edsl/surveys/instructions/__init__.py +0 -0
  315. edsl/templates/error_reporting/base.html +24 -0
  316. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  317. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  318. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  319. edsl/templates/error_reporting/interview_details.html +116 -0
  320. edsl/templates/error_reporting/interviews.html +19 -0
  321. edsl/templates/error_reporting/overview.html +5 -0
  322. edsl/templates/error_reporting/performance_plot.html +2 -0
  323. edsl/templates/error_reporting/report.css +74 -0
  324. edsl/templates/error_reporting/report.html +118 -0
  325. edsl/templates/error_reporting/report.js +25 -0
  326. edsl/tools/__init__.py +1 -0
  327. edsl/tools/clusters.py +192 -0
  328. edsl/tools/embeddings.py +27 -0
  329. edsl/tools/embeddings_plotting.py +118 -0
  330. edsl/tools/plotting.py +112 -0
  331. edsl/tools/summarize.py +18 -0
  332. edsl/utilities/PrettyList.py +56 -0
  333. edsl/utilities/SystemInfo.py +5 -0
  334. edsl/utilities/__init__.py +21 -20
  335. edsl/utilities/ast_utilities.py +3 -0
  336. edsl/utilities/data/Registry.py +2 -0
  337. edsl/utilities/decorators.py +41 -0
  338. edsl/utilities/gcp_bucket/__init__.py +0 -0
  339. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  340. edsl/utilities/interface.py +310 -60
  341. edsl/utilities/is_notebook.py +18 -0
  342. edsl/utilities/is_valid_variable_name.py +11 -0
  343. edsl/utilities/naming_utilities.py +263 -0
  344. edsl/utilities/remove_edsl_version.py +24 -0
  345. edsl/utilities/repair_functions.py +28 -0
  346. edsl/utilities/restricted_python.py +70 -0
  347. edsl/utilities/utilities.py +203 -13
  348. edsl-0.1.40.dist-info/METADATA +111 -0
  349. edsl-0.1.40.dist-info/RECORD +362 -0
  350. {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
  351. edsl/agents/AgentListExportMixin.py +0 -24
  352. edsl/coop/old.py +0 -31
  353. edsl/data/Database.py +0 -141
  354. edsl/data/crud.py +0 -121
  355. edsl/jobs/Interview.py +0 -435
  356. edsl/jobs/JobsRunner.py +0 -63
  357. edsl/jobs/JobsRunnerStatusMixin.py +0 -115
  358. edsl/jobs/base.py +0 -47
  359. edsl/jobs/buckets.py +0 -178
  360. edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
  361. edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
  362. edsl/jobs/task_management.py +0 -215
  363. edsl/jobs/token_tracking.py +0 -78
  364. edsl/language_models/DeepInfra.py +0 -69
  365. edsl/language_models/OpenAI.py +0 -98
  366. edsl/language_models/model_interfaces/GeminiPro.py +0 -66
  367. edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
  368. edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
  369. edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
  370. edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
  371. edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
  372. edsl/language_models/registry.py +0 -81
  373. edsl/language_models/schemas.py +0 -15
  374. edsl/language_models/unused/ReplicateBase.py +0 -83
  375. edsl/prompts/QuestionInstructionsBase.py +0 -6
  376. edsl/prompts/library/agent_instructions.py +0 -29
  377. edsl/prompts/library/agent_persona.py +0 -17
  378. edsl/prompts/library/question_budget.py +0 -26
  379. edsl/prompts/library/question_checkbox.py +0 -32
  380. edsl/prompts/library/question_extract.py +0 -19
  381. edsl/prompts/library/question_freetext.py +0 -14
  382. edsl/prompts/library/question_linear_scale.py +0 -20
  383. edsl/prompts/library/question_list.py +0 -22
  384. edsl/prompts/library/question_multiple_choice.py +0 -44
  385. edsl/prompts/library/question_numerical.py +0 -31
  386. edsl/prompts/library/question_rank.py +0 -21
  387. edsl/prompts/prompt_config.py +0 -33
  388. edsl/prompts/registry.py +0 -185
  389. edsl/questions/Question.py +0 -240
  390. edsl/report/InputOutputDataTypes.py +0 -134
  391. edsl/report/RegressionMixin.py +0 -28
  392. edsl/report/ReportOutputs.py +0 -1228
  393. edsl/report/ResultsFetchMixin.py +0 -106
  394. edsl/report/ResultsOutputMixin.py +0 -14
  395. edsl/report/demo.ipynb +0 -645
  396. edsl/results/ResultsDBMixin.py +0 -184
  397. edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
  398. edsl/trackers/Tracker.py +0 -91
  399. edsl/trackers/TrackerAPI.py +0 -196
  400. edsl/trackers/TrackerTasks.py +0 -70
  401. edsl/utilities/pastebin.py +0 -141
  402. edsl-0.1.15.dist-info/METADATA +0 -69
  403. edsl-0.1.15.dist-info/RECORD +0 -142
  404. /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
  405. /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
  406. /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
  407. {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/agents/Agent.py CHANGED
@@ -1,67 +1,84 @@
1
+ """An Agent is an AI agent that can reference a set of traits in answering questions."""
2
+
1
3
  from __future__ import annotations
2
4
  import copy
3
5
  import inspect
4
6
  import types
5
- import io
6
- from typing import Any, Callable, Optional, Union, Dict
7
+ from typing import (
8
+ Callable,
9
+ Optional,
10
+ Union,
11
+ Any,
12
+ TYPE_CHECKING,
13
+ Protocol,
14
+ runtime_checkable,
15
+ TypeVar,
16
+ )
17
+ from contextlib import contextmanager
18
+ from dataclasses import dataclass
19
+
20
+ # Type variable for the Agent class
21
+ A = TypeVar("A", bound="Agent")
22
+
23
+ if TYPE_CHECKING:
24
+ from edsl.data.Cache import Cache
25
+ from edsl.surveys.Survey import Survey
26
+ from edsl.scenarios.Scenario import Scenario
27
+ from edsl.language_models import LanguageModel
28
+ from edsl.surveys.MemoryPlan import MemoryPlan
29
+ from edsl.questions import QuestionBase
30
+ from edsl.agents.Invigilator import InvigilatorBase
31
+ from edsl.prompts import Prompt
32
+ from edsl.questions.QuestionBase import QuestionBase
33
+ from edsl.scenarios.Scenario import Scenario
34
+
7
35
 
8
- from jinja2 import Template
36
+ @runtime_checkable
37
+ class DirectAnswerMethod(Protocol):
38
+ """Protocol defining the required signature for direct answer methods."""
9
39
 
10
- from rich.console import Console
11
- from rich.table import Table
40
+ def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
41
+
42
+
43
+ from uuid import uuid4
12
44
 
13
45
  from edsl.Base import Base
46
+ from edsl.exceptions.questions import QuestionScenarioRenderError
14
47
 
15
48
  from edsl.exceptions.agents import (
49
+ AgentErrors,
16
50
  AgentCombinationError,
17
51
  AgentDirectAnswerFunctionError,
18
52
  AgentDynamicTraitsFunctionError,
19
53
  )
20
54
 
21
- from edsl.agents.Invigilator import (
22
- InvigilatorDebug,
23
- InvigilatorHuman,
24
- InvigilatorFunctional,
25
- InvigilatorAI,
26
- )
27
-
28
- from edsl.language_models.registry import Model
29
- from edsl.scenarios import Scenario
30
- from edsl.enums import LanguageModelType
31
-
32
- # from edsl.utilities import (
33
- # dict_to_html,
34
- # print_dict_as_html_table,
35
- # print_dict_with_rich,
36
- # )
37
-
38
55
  from edsl.agents.descriptors import (
39
56
  TraitsDescriptor,
40
57
  CodebookDescriptor,
41
58
  InstructionDescriptor,
42
59
  NameDescriptor,
43
60
  )
44
-
45
- from edsl.utilities.decorators import sync_wrapper
46
-
61
+ from edsl.utilities.decorators import (
62
+ sync_wrapper,
63
+ )
64
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
47
65
  from edsl.data_transfer_models import AgentResponseDict
66
+ from edsl.utilities.restricted_python import create_restricted_function
48
67
 
49
- from edsl.prompts.library.agent_persona import AgentPersona
68
+ from edsl.scenarios.Scenario import Scenario
50
69
 
51
70
 
52
- class Agent(Base):
53
- """An agent that can answer questions.
71
+ class AgentTraits(Scenario):
72
+ """A class representing the traits of an agent."""
54
73
 
55
- Parameters
56
- ----------
57
- traits : dict, optional - A dictionary of traits that the agent has. The keys need to be
58
- valid python variable names. The values can be any python object that has a valid __str__ method.
59
- codebook : dict, optional - A codebook mapping trait keys to trait descriptions.
60
- instruction : str, optional - Instructions for the agent.
74
+ def __repr__(self):
75
+ return f"{self.data}"
61
76
 
62
- dynamic_traits_function : Callable, optional - A function that returns a dictionary of traits.
63
77
 
64
- """
78
+ class Agent(Base):
79
+ """An class representing an agent that can answer questions."""
80
+
81
+ __documentation__ = "https://docs.expectedparrot.com/en/latest/agents.html"
65
82
 
66
83
  default_instruction = """You are answering questions as if you were a human. Do not break character."""
67
84
 
@@ -69,36 +86,250 @@ class Agent(Base):
69
86
  codebook = CodebookDescriptor()
70
87
  instruction = InstructionDescriptor()
71
88
  name = NameDescriptor()
89
+ dynamic_traits_function_name = ""
90
+ answer_question_directly_function_name = ""
91
+ has_dynamic_traits_function = False
72
92
 
73
93
  def __init__(
74
94
  self,
75
- *,
76
- traits: dict = None,
77
- name: str = None,
78
- codebook: dict = None,
79
- instruction: str = None,
80
- trait_presentation_template: str = None,
81
- dynamic_traits_function: Callable = None,
95
+ traits: Optional[dict] = None,
96
+ name: Optional[str] = None,
97
+ codebook: Optional[dict] = None,
98
+ instruction: Optional[str] = None,
99
+ traits_presentation_template: Optional[str] = None,
100
+ dynamic_traits_function: Optional[Callable] = None,
101
+ dynamic_traits_function_source_code: Optional[str] = None,
102
+ dynamic_traits_function_name: Optional[str] = None,
103
+ answer_question_directly_source_code: Optional[str] = None,
104
+ answer_question_directly_function_name: Optional[str] = None,
82
105
  ):
83
- self.name = name
84
- self._traits = traits or dict()
85
- self.codebook = codebook or dict()
86
- self.instruction = instruction or self.default_instruction
87
- self.dynamic_traits_function = dynamic_traits_function
106
+ """Initialize a new instance of Agent.
107
+
108
+ :param traits: A dictionary of traits that the agent has. The keys need to be valid identifiers.
109
+ :param name: A name for the agent
110
+ :param codebook: A codebook mapping trait keys to trait descriptions.
111
+ :param instruction: Instructions for the agent in how to answer questions.
112
+ :param trait_presentation_template: A template for how to present the agent's traits.
113
+ :param dynamic_traits_function: A function that returns a dictionary of traits.
114
+ :param dynamic_traits_function_source_code: The source code for the dynamic traits function.
115
+ :param dynamic_traits_function_name: The name of the dynamic traits function.
116
+
117
+ The `traits` parameter is a dictionary of traits that the agent has.
118
+ These traits are used to construct a prompt that is presented to the LLM.
119
+ In the absence of a `traits_presentation_template`, the default is used.
120
+ This is a template that is used to present the agent's traits to the LLM.
121
+ See :py:class:`edsl.prompts.library.agent_persona.AgentPersona` for more information.
122
+
123
+ Example usage:
124
+
125
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
126
+ >>> a.traits
127
+ {'age': 10, 'hair': 'brown', 'height': 5.5}
128
+
129
+ These traits are used to construct a prompt that is presented to the LLM.
130
+
131
+ In the absence of a `traits_presentation_template`, the default is used.
132
+
133
+ >>> a = Agent(traits = {"age": 10}, traits_presentation_template = "I am a {{age}} year old.")
134
+ >>> repr(a.agent_persona)
135
+ 'Prompt(text=\"""I am a {{age}} year old.\""")'
136
+
137
+ When this is rendered for presentation to the LLM, it will replace the `{{age}}` with the actual age.
138
+ it is also possible to use the `codebook` to provide a more human-readable description of the trait.
139
+ Here is an example where we give a prefix to the age trait (namely the age):
140
+
141
+ >>> traits = {"age": 10, "hair": "brown", "height": 5.5}
142
+ >>> codebook = {'age': 'Their age is'}
143
+ >>> a = Agent(traits = traits, codebook = codebook, traits_presentation_template = "This agent is Dave. {{codebook['age']}} {{age}}")
144
+ >>> d = a.traits | {'codebook': a.codebook}
145
+ >>> a.agent_persona.render(d)
146
+ Prompt(text=\"""This agent is Dave. Their age is 10\""")
147
+
148
+ Instructions
149
+ ------------
150
+ The agent can also have instructions. These are instructions that are given to the agent when answering questions.
151
+
152
+ >>> Agent.default_instruction
153
+ 'You are answering questions as if you were a human. Do not break character.'
154
+
155
+ See see how these are used to actually construct the prompt that is presented to the LLM, see :py:class:`edsl.agents.Invigilator.InvigilatorBase`.
156
+
157
+ """
158
+ self._initialize_basic_attributes(traits, name, codebook)
159
+ self._initialize_instruction(instruction)
160
+ self._initialize_dynamic_traits_function(
161
+ dynamic_traits_function,
162
+ dynamic_traits_function_source_code,
163
+ dynamic_traits_function_name,
164
+ )
165
+ self._initialize_answer_question_directly(
166
+ answer_question_directly_source_code, answer_question_directly_function_name
167
+ )
88
168
  self._check_dynamic_traits_function()
169
+ self._initialize_traits_presentation_template(traits_presentation_template)
89
170
  self.current_question = None
90
171
 
91
- if trait_presentation_template is not None:
92
- self.trait_presentation_template = trait_presentation_template
93
- self.agent_persona = AgentPersona(text=self.trait_presentation_template)
172
+ def _initialize_basic_attributes(self, traits, name, codebook) -> None:
173
+ """Initialize the basic attributes of the agent."""
174
+ self.name = name
175
+ self._traits = AgentTraits(traits or dict())
176
+ self.codebook = codebook or dict()
177
+
178
+ def _initialize_instruction(self, instruction) -> None:
179
+ """Initialize the instruction for the agent i.e., how the agent should answer questions."""
180
+ if instruction is None:
181
+ self.instruction = self.default_instruction
182
+ self._instruction = self.default_instruction
183
+ self.set_instructions = False
184
+ else:
185
+ self.instruction = instruction
186
+ self._instruction = instruction
187
+ self.set_instructions = True
188
+
189
+ def _initialize_traits_presentation_template(
190
+ self, traits_presentation_template
191
+ ) -> None:
192
+ """Initialize the traits presentation template. How the agent's traits are presented to the LLM."""
193
+ if traits_presentation_template is not None:
194
+ self._traits_presentation_template = traits_presentation_template
195
+ self.traits_presentation_template = traits_presentation_template
196
+ self.set_traits_presentation_template = True
197
+ else:
198
+ self.traits_presentation_template = "Your traits: {{traits}}"
199
+ self.set_traits_presentation_template = False
200
+
201
+ def _initialize_dynamic_traits_function(
202
+ self,
203
+ dynamic_traits_function,
204
+ dynamic_traits_function_source_code,
205
+ dynamic_traits_function_name,
206
+ ) -> None:
207
+ """Initialize the dynamic traits function i.e., a function that returns a dictionary of traits based on the question."""
208
+ # Deal with dynamic traits function
209
+ self.dynamic_traits_function = dynamic_traits_function
94
210
 
95
- def _check_dynamic_traits_function(self):
96
211
  if self.dynamic_traits_function:
212
+ self.dynamic_traits_function_name = self.dynamic_traits_function.__name__
213
+ self.has_dynamic_traits_function = True
214
+ else:
215
+ self.has_dynamic_traits_function = False
216
+
217
+ if dynamic_traits_function_source_code:
218
+ self.dynamic_traits_function_name = dynamic_traits_function_name
219
+ self.dynamic_traits_function = create_restricted_function(
220
+ dynamic_traits_function_name, dynamic_traits_function
221
+ )
222
+
223
+ def _initialize_answer_question_directly(
224
+ self,
225
+ answer_question_directly_source_code,
226
+ answer_question_directly_function_name,
227
+ ) -> None:
228
+ if answer_question_directly_source_code:
229
+ self.answer_question_directly_function_name = (
230
+ answer_question_directly_function_name
231
+ )
232
+ protected_method = create_restricted_function(
233
+ answer_question_directly_function_name,
234
+ answer_question_directly_source_code,
235
+ )
236
+ bound_method = types.MethodType(protected_method, self)
237
+ setattr(self, "answer_question_directly", bound_method)
238
+
239
+ def _initialize_traits_presentation_template(
240
+ self, traits_presentation_template
241
+ ) -> None:
242
+ if traits_presentation_template is not None:
243
+ self._traits_presentation_template = traits_presentation_template
244
+ self.traits_presentation_template = traits_presentation_template
245
+ self.set_traits_presentation_template = True
246
+ else:
247
+ self.traits_presentation_template = "Your traits: {{traits}}"
248
+ self.set_traits_presentation_template = False
249
+
250
+ def duplicate(self) -> Agent:
251
+ """Return a duplicate of the agent.
252
+
253
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
254
+ >>> a2 = a.duplicate()
255
+ >>> a2 == a
256
+ True
257
+ >>> id(a) == id(a2)
258
+ False
259
+ >>> def f(self, question, scenario): return "I am a direct answer."
260
+ >>> a.add_direct_question_answering_method(f)
261
+ >>> hasattr(a, "answer_question_directly")
262
+ True
263
+ >>> a2 = a.duplicate()
264
+ >>> a2.answer_question_directly(None, None)
265
+ 'I am a direct answer.'
266
+
267
+ >>> a = Agent(traits = {'age': 10}, instruction = "Have fun!")
268
+ >>> a2 = a.duplicate()
269
+ >>> a2.instruction
270
+ 'Have fun!'
271
+ """
272
+ new_agent = Agent.from_dict(self.to_dict())
273
+ if hasattr(self, "answer_question_directly"):
274
+ answer_question_directly = self.answer_question_directly
275
+ newf = lambda self, question, scenario: answer_question_directly(
276
+ question, scenario
277
+ )
278
+ new_agent.add_direct_question_answering_method(newf)
279
+ if hasattr(self, "dynamic_traits_function"):
280
+ dynamic_traits_function = self.dynamic_traits_function
281
+ new_agent.dynamic_traits_function = dynamic_traits_function
282
+ return new_agent
283
+
284
+ @property
285
+ def agent_persona(self) -> Prompt:
286
+ """Return the agent persona template."""
287
+ from edsl.prompts.Prompt import Prompt
288
+
289
+ return Prompt(text=self.traits_presentation_template)
290
+
291
+ def prompt(self) -> str:
292
+ """Return the prompt for the agent.
293
+
294
+ Example usage:
295
+
296
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
297
+ >>> a.prompt()
298
+ Prompt(text=\"""Your traits: {'age': 10, 'hair': 'brown', 'height': 5.5}\""")
299
+ """
300
+ replacement_dict = (
301
+ self.traits | {"traits": self.traits} | {"codebook": self.codebook}
302
+ )
303
+ if undefined := self.agent_persona.undefined_template_variables(
304
+ replacement_dict
305
+ ):
306
+ raise QuestionScenarioRenderError(
307
+ f"Agent persona still has variables that were not rendered: {undefined}"
308
+ )
309
+ else:
310
+ return self.agent_persona.render(replacement_dict)
311
+
312
+ def _check_dynamic_traits_function(self) -> None:
313
+ """Check whether dynamic trait function is valid.
314
+
315
+ This checks whether the dynamic traits function is valid.
316
+
317
+ >>> def f(question): return {"age": 10, "hair": "brown", "height": 5.5}
318
+ >>> a = Agent(dynamic_traits_function = f)
319
+ >>> a._check_dynamic_traits_function()
320
+
321
+ >>> def g(question, poo): return {"age": 10, "hair": "brown", "height": 5.5}
322
+ >>> a = Agent(dynamic_traits_function = g)
323
+ Traceback (most recent call last):
324
+ ...
325
+ edsl.exceptions.agents.AgentDynamicTraitsFunctionError: ...
326
+ """
327
+ if self.has_dynamic_traits_function:
97
328
  sig = inspect.signature(self.dynamic_traits_function)
98
329
  if "question" in sig.parameters:
99
330
  if len(sig.parameters) > 1:
100
331
  raise AgentDynamicTraitsFunctionError(
101
- f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
332
+ message=f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
102
333
  )
103
334
  else:
104
335
  if len(sig.parameters) > 0:
@@ -109,35 +340,196 @@ class Agent(Base):
109
340
 
110
341
  @property
111
342
  def traits(self) -> dict[str, str]:
112
- """A agent's traits, which is a dictionary.
343
+ """An agent's traits, which is a dictionary.
113
344
 
114
- >> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
115
- >> a.traits
116
- {'age': 10, 'hair': 'brown', 'height': 5.5}
345
+ The agent could have a a dynamic traits function (`dynamic_traits_function`) that returns a dictionary of traits
346
+ when called. This function can also take a `question` as an argument.
347
+ If so, the dynamic traits function is called and the result is returned.
348
+ Otherwise, the traits are returned.
117
349
 
118
- >> a = Agent()
119
- >> a.add_direct_question_answering_method(lambda question, scenario, self: {"age": 10, "hair": "brown", "height": 5.5})
120
- >> a.traits
350
+ Example:
351
+
352
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
353
+ >>> a.traits
121
354
  {'age': 10, 'hair': 'brown', 'height': 5.5}
122
355
 
123
- The agent could have a a dynamic traits function (dynamic_traits_function) that returns a dictionary of traits
124
- when called. This function can also take a question as an argument.
125
- If so, the dynamic traits function is called and the result is returned.
126
- Otherwise, the traits are returned.
127
356
  """
128
- if self.dynamic_traits_function:
357
+ if self.has_dynamic_traits_function:
129
358
  sig = inspect.signature(self.dynamic_traits_function)
130
359
  if "question" in sig.parameters:
131
360
  return self.dynamic_traits_function(question=self.current_question)
132
361
  else:
133
362
  return self.dynamic_traits_function()
134
363
  else:
135
- return self._traits
364
+ return dict(self._traits)
365
+
366
+ @contextmanager
367
+ def modify_traits_context(self):
368
+ self._check_before_modifying_traits()
369
+ try:
370
+ yield
371
+ finally:
372
+ self._traits = AgentTraits(self._traits)
373
+
374
+ def _check_before_modifying_traits(self):
375
+ """Check before modifying traits."""
376
+ if self.has_dynamic_traits_function:
377
+ raise AgentErrors(
378
+ "You cannot modify the traits of an agent that has a dynamic traits function.",
379
+ "If you want to modify the traits, you should remove the dynamic traits function.",
380
+ )
381
+
382
+ @traits.setter
383
+ def traits(self, traits: dict[str, str]):
384
+ with self.modify_traits_context():
385
+ self._traits = traits
386
+ # self._check_before_modifying_traits()
387
+ # self._traits = AgentTraits(traits)
388
+
389
+ def rename(
390
+ self,
391
+ old_name_or_dict: Union[str, dict[str, str]],
392
+ new_name: Optional[str] = None,
393
+ ) -> Agent:
394
+ """Rename a trait.
395
+
396
+ :param old_name_or_dict: The old name of the trait or a dictionary of old names and new names.
397
+ :param new_name: The new name of the trait.
398
+
399
+ Example usage:
400
+
401
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
402
+ >>> newa = a.rename("age", "years")
403
+ >>> newa == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
404
+ True
405
+
406
+ >>> newa.rename({'years': 'smage'}) == Agent(traits = {'smage': 10, 'hair': 'brown', 'height': 5.5})
407
+ True
408
+
409
+ """
410
+ self._check_before_modifying_traits()
411
+ if isinstance(old_name_or_dict, dict) and new_name:
412
+ raise AgentErrors(
413
+ f"You passed a dict: {old_name_or_dict} and a new name: {new_name}. You should pass only a dict."
414
+ )
415
+
416
+ if isinstance(old_name_or_dict, dict) and new_name is None:
417
+ return self._rename_dict(old_name_or_dict)
418
+
419
+ if isinstance(old_name_or_dict, str):
420
+ return self._rename(old_name_or_dict, new_name)
421
+
422
+ raise AgentErrors("Something is not right with Agent renaming")
423
+
424
+ def _rename_dict(self, renaming_dict: dict[str, str]) -> Agent:
425
+ """
426
+ Internal method to rename traits using a dictionary.
427
+ The keys should all be old names and the values should all be new names.
428
+
429
+ Example usage:
430
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
431
+ >>> a._rename_dict({"age": "years", "height": "feet"})
432
+ Agent(traits = {'years': 10, 'hair': 'brown', 'feet': 5.5})
433
+
434
+ """
435
+ try:
436
+ assert all(k in self.traits for k in renaming_dict.keys())
437
+ except AssertionError:
438
+ raise AgentErrors(
439
+ f"The trait(s) {set(renaming_dict.keys()) - set(self.traits.keys())} do not exist in the agent's traits, which are {self.traits}."
440
+ )
441
+ new_agent = self.duplicate()
442
+ new_agent.traits = {renaming_dict.get(k, k): v for k, v in self.traits.items()}
443
+ return new_agent
444
+
445
+ def _rename(self, old_name: str, new_name: str) -> Agent:
446
+ """Rename a trait.
447
+
448
+ Example usage:
449
+
450
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
451
+ >>> a._rename(old_name="age", new_name="years")
452
+ Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
453
+
454
+ """
455
+ try:
456
+ assert old_name in self.traits
457
+ except AssertionError:
458
+ raise AgentErrors(
459
+ f"The trait '{old_name}' does not exist in the agent's traits, which are {self.traits}."
460
+ )
461
+ newagent = self.duplicate()
462
+ newagent.traits = {
463
+ new_name if k == old_name else k: v for k, v in self.traits.items()
464
+ }
465
+ newagent.codebook = {
466
+ new_name if k == old_name else k: v for k, v in self.codebook.items()
467
+ }
468
+ return newagent
469
+
470
+ def __getitem__(self, key):
471
+ """Allow for accessing traits using the bracket notation.
472
+
473
+ Example:
474
+
475
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
476
+ >>> a['traits']['age']
477
+ 10
478
+
479
+ """
480
+ return getattr(self, key)
481
+
482
+ def remove_direct_question_answering_method(self) -> None:
483
+ """Remove the direct question answering method.
136
484
 
137
- def add_direct_question_answering_method(self, method: Callable):
138
- """Adds a method to the agent that can answer a particular question type."""
485
+ Example usage:
486
+
487
+ >>> a = Agent()
488
+ >>> def f(self, question, scenario): return "I am a direct answer."
489
+ >>> a.add_direct_question_answering_method(f)
490
+ >>> a.remove_direct_question_answering_method()
491
+ >>> hasattr(a, "answer_question_directly")
492
+ False
493
+ """
494
+ if hasattr(self, "answer_question_directly"):
495
+ delattr(self, "answer_question_directly")
496
+
497
+ def add_direct_question_answering_method(
498
+ self,
499
+ method: DirectAnswerMethod,
500
+ validate_response: bool = False,
501
+ translate_response: bool = False,
502
+ ) -> None:
503
+ """Add a method to the agent that can answer a particular question type.
504
+ https://docs.expectedparrot.com/en/latest/agents.html#agent-direct-answering-methods
505
+
506
+ :param method: A method that can answer a question directly.
507
+ :param validate_response: Whether to validate the response.
508
+ :param translate_response: Whether to translate the response.
509
+
510
+ Example usage:
511
+
512
+ >>> a = Agent()
513
+ >>> def f(self, question, scenario): return "I am a direct answer."
514
+ >>> a.add_direct_question_answering_method(f)
515
+ >>> a.answer_question_directly(question = None, scenario = None)
516
+ 'I am a direct answer.'
517
+ """
139
518
  if hasattr(self, "answer_question_directly"):
140
- print("Warning: overwriting existing answer_question_directly method")
519
+ import warnings
520
+
521
+ warnings.warn(
522
+ "Warning: overwriting existing answer_question_directly method"
523
+ )
524
+
525
+ self.validate_response = validate_response
526
+ self.translate_response = translate_response
527
+
528
+ # if not isinstance(method, DirectAnswerMethod):
529
+ # raise AgentDirectAnswerFunctionError(
530
+ # f"Method {method} does not match required signature. "
531
+ # "Must take (self, question, scenario) parameters."
532
+ # )
141
533
 
142
534
  signature = inspect.signature(method)
143
535
  for argument in ["question", "scenario", "self"]:
@@ -147,93 +539,215 @@ class Agent(Base):
147
539
  )
148
540
  bound_method = types.MethodType(method, self)
149
541
  setattr(self, "answer_question_directly", bound_method)
542
+ self.answer_question_directly_function_name = bound_method.__name__
150
543
 
151
544
  def create_invigilator(
152
545
  self,
153
- question: Question,
154
- scenario: Optional[Scenario] = None,
155
- model: Optional[LanguageModel] = None,
156
- debug: bool = False,
157
- memory_plan: Optional[MemoryPlan] = None,
546
+ *,
547
+ question: "QuestionBase",
548
+ cache: "Cache",
549
+ survey: Optional["Survey"] = None,
550
+ scenario: Optional["Scenario"] = None,
551
+ model: Optional["LanguageModel"] = None,
552
+ memory_plan: Optional["MemoryPlan"] = None,
158
553
  current_answers: Optional[dict] = None,
159
- ) -> "Invigilator":
160
- """
161
- An invigator is an object that is responsible administering a question to an agent and
554
+ iteration: int = 1,
555
+ raise_validation_errors: bool = True,
556
+ key_lookup: Optional["KeyLookup"] = None,
557
+ ) -> "InvigilatorBase":
558
+ """Create an Invigilator.
559
+
560
+ An invigilator is an object that is responsible for administering a question to an agent.
561
+ There are several different types of invigilators, depending on the type of question and the agent.
562
+ For example, there are invigilators for functional questions (i.e., question is of type :class:`edsl.questions.QuestionFunctional`:), for direct questions, and for LLM questions.
563
+
564
+ >>> a = Agent(traits = {})
565
+ >>> a.create_invigilator(question = None, cache = False)
566
+ InvigilatorAI(...)
567
+
568
+ An invigator is an object that is responsible for administering a question to an agent and
162
569
  recording the responses.
163
570
  """
571
+ from edsl.language_models.model import Model
572
+
573
+ from edsl.scenarios.Scenario import Scenario
574
+
575
+ cache = cache
164
576
  self.current_question = question
165
- model = model or Model(LanguageModelType.GPT_4.value, use_cache=True)
577
+ model = model or Model()
166
578
  scenario = scenario or Scenario()
167
579
  invigilator = self._create_invigilator(
168
- question, scenario, model, debug, memory_plan, current_answers
580
+ question=question,
581
+ scenario=scenario,
582
+ survey=survey,
583
+ model=model,
584
+ memory_plan=memory_plan,
585
+ current_answers=current_answers,
586
+ iteration=iteration,
587
+ cache=cache,
588
+ raise_validation_errors=raise_validation_errors,
589
+ key_lookup=key_lookup,
169
590
  )
591
+ if hasattr(self, "validate_response"):
592
+ invigilator.validate_response = self.validate_response
593
+ if hasattr(self, "translate_response"):
594
+ invigilator.translate_response = self.translate_response
170
595
  return invigilator
171
596
 
172
597
  async def async_answer_question(
173
598
  self,
174
- question: Question,
599
+ *,
600
+ question: QuestionBase,
601
+ cache: Cache,
175
602
  scenario: Optional[Scenario] = None,
603
+ survey: Optional[Survey] = None,
176
604
  model: Optional[LanguageModel] = None,
177
605
  debug: bool = False,
178
606
  memory_plan: Optional[MemoryPlan] = None,
179
607
  current_answers: Optional[dict] = None,
608
+ iteration: int = 0,
609
+ key_lookup: Optional["KeyLookup"] = None,
180
610
  ) -> AgentResponseDict:
181
611
  """
612
+ Answer a posed question.
613
+
614
+ :param question: The question to answer.
615
+ :param scenario: The scenario in which the question is asked.
616
+ :param model: The language model to use.
617
+ :param debug: Whether to run in debug mode.
618
+ :param memory_plan: The memory plan to use.
619
+ :param current_answers: The current answers.
620
+ :param iteration: The iteration number.
621
+
622
+ >>> a = Agent(traits = {})
623
+ >>> a.add_direct_question_answering_method(lambda self, question, scenario: "I am a direct answer.")
624
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
625
+ >>> q = QuestionFreeText.example()
626
+ >>> a.answer_question(question = q, cache = False).answer
627
+ 'I am a direct answer.'
628
+
182
629
  This is a function where an agent returns an answer to a particular question.
183
630
  However, there are several different ways an agent can answer a question, so the
184
- actual functionality is delegated to an Invigilator object.
631
+ actual functionality is delegated to an :class:`edsl.agents.InvigilatorBase`: object.
185
632
  """
186
633
  invigilator = self.create_invigilator(
187
634
  question=question,
635
+ cache=cache,
188
636
  scenario=scenario,
637
+ survey=survey,
189
638
  model=model,
190
- debug=debug,
191
639
  memory_plan=memory_plan,
192
640
  current_answers=current_answers,
641
+ iteration=iteration,
642
+ key_lookup=key_lookup,
193
643
  )
194
644
  response: AgentResponseDict = await invigilator.async_answer_question()
195
645
  return response
196
646
 
197
647
  answer_question = sync_wrapper(async_answer_question)
198
648
 
649
+ def _get_invigilator_class(self, question: QuestionBase) -> Type[InvigilatorBase]:
650
+ """Get the invigilator class for a question.
651
+
652
+ This method returns the invigilator class that should be used to answer a question.
653
+ The invigilator class is determined by the type of question and the type of agent.
654
+ """
655
+ from edsl.agents.Invigilator import (
656
+ InvigilatorHuman,
657
+ InvigilatorFunctional,
658
+ InvigilatorAI,
659
+ )
660
+
661
+ if hasattr(question, "answer_question_directly"):
662
+ return InvigilatorFunctional
663
+ elif hasattr(self, "answer_question_directly"):
664
+ return InvigilatorHuman
665
+ else:
666
+ return InvigilatorAI
667
+
199
668
  def _create_invigilator(
200
669
  self,
201
- question: Question,
670
+ question: QuestionBase,
671
+ cache: Optional[Cache] = None,
202
672
  scenario: Optional[Scenario] = None,
203
673
  model: Optional[LanguageModel] = None,
204
- debug: bool = False,
674
+ survey: Optional[Survey] = None,
205
675
  memory_plan: Optional[MemoryPlan] = None,
206
676
  current_answers: Optional[dict] = None,
207
- ):
208
- model = model or Model(LanguageModelType.GPT_4.value, use_cache=True)
677
+ iteration: int = 0,
678
+ raise_validation_errors: bool = True,
679
+ key_lookup: Optional["KeyLookup"] = None,
680
+ ) -> "InvigilatorBase":
681
+ """Create an Invigilator."""
682
+ from edsl.language_models.model import Model
683
+ from edsl.scenarios.Scenario import Scenario
684
+
685
+ model = model or Model()
209
686
  scenario = scenario or Scenario()
210
687
 
211
- if debug:
212
- # use the question's simulate_answer method
213
- invigilator_class = InvigilatorDebug
214
- elif hasattr(question, "answer_question_directly"):
215
- # it is a functional question and the answer only depends on the agent's traits & the scenario
216
- invigilator_class = InvigilatorFunctional
217
- elif hasattr(self, "answer_question_directly"):
218
- # this of the case where the agent has a method that can answer the question directly
219
- # this occurrs when 'answer_question_directly' has been monkey-patched onto the agent
220
- # which happens when the agent is created from an existing survey
221
- invigilator_class = InvigilatorHuman
222
- else:
223
- # this means an LLM agent will be used. This is the standard case.
224
- invigilator_class = InvigilatorAI
688
+ if cache is None:
689
+ from edsl.data.Cache import Cache
690
+
691
+ cache = Cache()
692
+
693
+ invigilator_class = self._get_invigilator_class(question)
225
694
 
226
695
  invigilator = invigilator_class(
227
- self, question, scenario, model, memory_plan, current_answers
696
+ self,
697
+ question=question,
698
+ scenario=scenario,
699
+ survey=survey,
700
+ model=model,
701
+ memory_plan=memory_plan,
702
+ current_answers=current_answers,
703
+ iteration=iteration,
704
+ cache=cache,
705
+ raise_validation_errors=raise_validation_errors,
706
+ key_lookup=key_lookup,
228
707
  )
229
708
  return invigilator
230
709
 
231
- ################
232
- # Dunder Methods
233
- ################
234
- def __add__(self, other_agent: Agent = None) -> Agent:
710
+ def select(self, *traits: str) -> Agent:
711
+ """Selects agents with only the references traits
712
+
713
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
714
+ >>> a
715
+ Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5}, codebook = {'age': 'Their age is'})
716
+
717
+
718
+ >>> a.select("age", "height")
719
+ Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is'})
720
+
721
+ >>> a.select("height")
722
+ Agent(traits = {'height': 5.5})
723
+
724
+ """
725
+
726
+ if len(traits) == 1:
727
+ traits_to_select = [list(traits)[0]]
728
+ else:
729
+ traits_to_select = list(traits)
730
+
731
+ def _remove_none(d):
732
+ return {k: v for k, v in d.items() if v is not None}
733
+
734
+ newagent = self.duplicate()
735
+ newagent.traits = {
736
+ trait: self.traits.get(trait, None) for trait in traits_to_select
737
+ }
738
+ newagent.codebook = _remove_none(
739
+ {trait: self.codebook.get(trait, None) for trait in traits_to_select}
740
+ )
741
+ return newagent
742
+
743
+ def __add__(self, other_agent: Optional[Agent] = None) -> Agent:
235
744
  """
236
- Combines two agents by joining their traits.The agents must not have overlapping traits.
745
+ Combine two agents by joining their traits.
746
+
747
+ The agents must not have overlapping traits.
748
+
749
+ Example usage:
750
+
237
751
  >>> a1 = Agent(traits = {"age": 10})
238
752
  >>> a2 = Agent(traits = {"height": 5.5})
239
753
  >>> a1 + a2
@@ -242,6 +756,11 @@ class Agent(Base):
242
756
  Traceback (most recent call last):
243
757
  ...
244
758
  edsl.exceptions.agents.AgentCombinationError: The agents have overlapping traits: {'age'}.
759
+ ...
760
+ >>> a1 = Agent(traits = {"age": 10}, codebook = {"age": "Their age is"})
761
+ >>> a2 = Agent(traits = {"height": 5.5}, codebook = {"height": "Their height is"})
762
+ >>> a1 + a2
763
+ Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is', 'height': 'Their height is'})
245
764
  """
246
765
  if other_agent is None:
247
766
  return self
@@ -250,12 +769,19 @@ class Agent(Base):
250
769
  f"The agents have overlapping traits: {common_traits}."
251
770
  )
252
771
  else:
253
- new_agent = Agent(traits=copy.deepcopy(self.traits))
254
- new_agent.traits.update(other_agent.traits)
255
- return new_agent
772
+ new_codebook = copy.deepcopy(self.codebook) | copy.deepcopy(
773
+ other_agent.codebook
774
+ )
775
+ d = self.traits | other_agent.traits
776
+ newagent = self.duplicate()
777
+ newagent.traits = d
778
+ newagent.codebook = new_codebook
779
+ return newagent
256
780
 
257
781
  def __eq__(self, other: Agent) -> bool:
258
- """Checks if two agents are equal. Only checks the traits.
782
+ """Check if two agents are equal.
783
+
784
+ This only checks the traits.
259
785
  >>> a1 = Agent(traits = {"age": 10})
260
786
  >>> a2 = Agent(traits = {"age": 10})
261
787
  >>> a1 == a2
@@ -266,25 +792,57 @@ class Agent(Base):
266
792
  """
267
793
  return self.data == other.data
268
794
 
269
- def __repr__(self):
795
+ def __getattr__(self, name):
796
+ """
797
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
798
+ >>> a.age
799
+ 10
800
+ """
801
+ if name == "has_dynamic_traits_function":
802
+ return self.has_dynamic_traits_function
803
+
804
+ if name in self._traits:
805
+ return self._traits[name]
806
+
807
+ raise AttributeError(
808
+ f"'{type(self).__name__}' object has no attribute '{name}'"
809
+ )
810
+
811
+ def __getstate__(self):
812
+ state = self.__dict__.copy()
813
+ # Include any additional state that needs to be serialized
814
+ return state
815
+
816
+ def __setstate__(self, state):
817
+ self.__dict__.update(state)
818
+ # Ensure _traits is initialized if it's missing
819
+ if "_traits" not in self.__dict__:
820
+ self._traits = {}
821
+
822
+ def __repr__(self) -> str:
823
+ """Return representation of Agent."""
270
824
  class_name = self.__class__.__name__
271
825
  items = [
272
- f"{k} = '{v}'" if isinstance(v, str) else f"{k} = {v}"
826
+ f'{k} = """{v}"""' if isinstance(v, str) else f"{k} = {v}"
273
827
  for k, v in self.data.items()
274
828
  if k != "question_type"
275
829
  ]
276
830
  return f"{class_name}({', '.join(items)})"
277
831
 
278
- ################
279
- # SERIALIZATION METHODS
280
- ################
281
832
  @property
282
- def data(self):
833
+ def data(self) -> dict:
834
+ """Format the data for serialization.
835
+
836
+ TODO: Warn if has dynamic traits function or direct answer function that cannot be serialized.
837
+ TODO: Add ability to have coop-hosted functions that are serializable.
838
+ """
839
+
283
840
  raw_data = {
284
841
  k.replace("_", "", 1): v
285
842
  for k, v in self.__dict__.items()
286
843
  if k.startswith("_")
287
844
  }
845
+
288
846
  if hasattr(self, "set_instructions"):
289
847
  if not self.set_instructions:
290
848
  raw_data.pop("instruction")
@@ -292,20 +850,102 @@ class Agent(Base):
292
850
  raw_data.pop("codebook")
293
851
  if self.name == None:
294
852
  raw_data.pop("name")
853
+
854
+ if hasattr(self, "dynamic_traits_function"):
855
+ raw_data.pop(
856
+ "dynamic_traits_function", None
857
+ ) # in case dynamic_traits_function will appear with _ in self.__dict__
858
+ dynamic_traits_func = self.dynamic_traits_function
859
+ if dynamic_traits_func:
860
+ func = inspect.getsource(dynamic_traits_func)
861
+ raw_data["dynamic_traits_function_source_code"] = func
862
+ raw_data["dynamic_traits_function_name"] = (
863
+ self.dynamic_traits_function_name
864
+ )
865
+ if hasattr(self, "answer_question_directly"):
866
+ raw_data.pop(
867
+ "answer_question_directly", None
868
+ ) # in case answer_question_directly will appear with _ in self.__dict__
869
+ answer_question_directly_func = self.answer_question_directly
870
+
871
+ if (
872
+ answer_question_directly_func
873
+ and raw_data.get("answer_question_directly_source_code", None) != None
874
+ ):
875
+ raw_data["answer_question_directly_source_code"] = inspect.getsource(
876
+ answer_question_directly_func
877
+ )
878
+ raw_data["answer_question_directly_function_name"] = (
879
+ self.answer_question_directly_function_name
880
+ )
881
+ raw_data["traits"] = dict(raw_data["traits"])
882
+
295
883
  return raw_data
296
884
 
297
- def to_dict(self) -> dict[str, Union[dict, bool]]:
298
- """Serializes to a dictionary."""
299
- return self.data
885
+ def __hash__(self) -> int:
886
+ """Return a hash of the agent.
887
+
888
+ >>> hash(Agent.example())
889
+ 2067581884874391607
890
+ """
891
+ from edsl.utilities.utilities import dict_hash
892
+
893
+ return dict_hash(self.to_dict(add_edsl_version=False))
894
+
895
+ def to_dict(self, add_edsl_version=True) -> dict[str, Union[dict, bool]]:
896
+ """Serialize to a dictionary with EDSL info.
897
+
898
+ Example usage:
899
+
900
+ >>> a = Agent(name = "Steve", traits = {"age": 10, "hair": "brown", "height": 5.5})
901
+ >>> a.to_dict()
902
+ {'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'name': 'Steve', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
903
+
904
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, instruction = "Have fun.")
905
+ >>> a.to_dict()
906
+ {'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'instruction': 'Have fun.', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
907
+ """
908
+ d = {}
909
+ d["traits"] = copy.deepcopy(dict(self._traits))
910
+ if self.name:
911
+ d["name"] = self.name
912
+ if self.set_instructions:
913
+ d["instruction"] = self.instruction
914
+ if self.set_traits_presentation_template:
915
+ d["traits_presentation_template"] = self.traits_presentation_template
916
+ if self.codebook:
917
+ d["codebook"] = self.codebook
918
+ if add_edsl_version:
919
+ from edsl import __version__
920
+
921
+ d["edsl_version"] = __version__
922
+ d["edsl_class_name"] = self.__class__.__name__
923
+
924
+ return d
300
925
 
301
926
  @classmethod
927
+ @remove_edsl_version
302
928
  def from_dict(cls, agent_dict: dict[str, Union[dict, bool]]) -> Agent:
303
- """Deserializes from a dictionary."""
304
- return cls(**agent_dict)
929
+ """Deserialize from a dictionary.
930
+
931
+ Example usage:
305
932
 
306
- ################
307
- # DISPLAY Methods
308
- ################
933
+ >>> Agent.from_dict({'name': "Steve", 'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}})
934
+ Agent(name = \"""Steve\""", traits = {'age': 10, 'hair': 'brown', 'height': 5.5})
935
+
936
+ """
937
+ if "traits" in agent_dict:
938
+ return cls(
939
+ traits=agent_dict["traits"],
940
+ name=agent_dict.get("name", None),
941
+ instruction=agent_dict.get("instruction", None),
942
+ traits_presentation_template=agent_dict.get(
943
+ "traits_presentation_template", None
944
+ ),
945
+ codebook=agent_dict.get("codebook", None),
946
+ )
947
+ else: # old-style agent - we used to only store the traits
948
+ return cls(**agent_dict)
309
949
 
310
950
  def _table(self) -> tuple[dict, list]:
311
951
  """Prepare generic table data."""
@@ -315,35 +955,95 @@ class Agent(Base):
315
955
  column_names = ["Attribute", "Value"]
316
956
  return table_data, column_names
317
957
 
318
- def rich_print(self):
319
- """Displays an object as a rich table."""
320
- table_data, column_names = self._table()
321
- table = Table(title=f"{self.__class__.__name__} Attributes")
322
- for column in column_names:
323
- table.add_column(column, style="bold")
958
+ def add_trait(self, trait_name_or_dict: str, value: Optional[Any] = None) -> Agent:
959
+ """Adds a trait to an agent and returns that agent
960
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
961
+ >>> a.add_trait("weight", 150)
962
+ Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5, 'weight': 150})
963
+ """
964
+ if isinstance(trait_name_or_dict, dict) and value is None:
965
+ newagent = self.duplicate()
966
+ newagent.traits = {**self.traits, **trait_name_or_dict}
967
+ return newagent
968
+
969
+ if isinstance(trait_name_or_dict, dict) and value:
970
+ raise AgentErrors(
971
+ f"You passed a dict: {trait_name_or_dict} and a value: {value}. You should pass only a dict."
972
+ )
973
+
974
+ if isinstance(trait_name_or_dict, str):
975
+ newagent = self.duplicate()
976
+ newagent.traits = {**self.traits, **{trait_name_or_dict: value}}
977
+ return newagent
978
+
979
+ raise AgentErrors("Something is not right with adding a trait to an Agent")
324
980
 
325
- for row in table_data:
326
- row_data = [row[column] for column in column_names]
327
- table.add_row(*row_data)
981
+ def remove_trait(self, trait: str) -> Agent:
982
+ """Remove a trait from the agent.
328
983
 
329
- return table
984
+ Example usage:
985
+
986
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
987
+ >>> a.remove_trait("age")
988
+ Agent(traits = {'hair': 'brown', 'height': 5.5})
989
+ """
990
+ newagent = self.duplicate()
991
+ newagent.traits = {k: v for k, v in self.traits.items() if k != trait}
992
+ return newagent
993
+
994
+ def translate_traits(self, values_codebook: dict) -> Agent:
995
+ """Translate traits to a new codebook.
996
+
997
+ >>> a = Agent(traits = {"age": 10, "hair": 1, "height": 5.5})
998
+ >>> a.translate_traits({"hair": {1:"brown"}})
999
+ Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5})
1000
+
1001
+ :param values_codebook: The new codebook.
1002
+ """
1003
+ new_traits = {}
1004
+ for key, value in self.traits.items():
1005
+ if key in values_codebook:
1006
+ new_traits[key] = values_codebook[key].get(value, value)
1007
+ else:
1008
+ new_traits[key] = value
1009
+ newagent = self.duplicate()
1010
+ newagent.traits = new_traits
1011
+ return newagent
330
1012
 
331
1013
  @classmethod
332
- def example(cls) -> Agent:
333
- """Returns an example agent.
1014
+ def example(cls, randomize: bool = False) -> Agent:
1015
+ """
1016
+ Returns an example Agent instance.
1017
+
1018
+ :param randomize: If True, adds a random string to the value of an example key.
334
1019
 
335
1020
  >>> Agent.example()
336
1021
  Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})
337
1022
  """
338
- return cls(traits={"age": 22, "hair": "brown", "height": 5.5})
1023
+ addition = "" if not randomize else str(uuid4())
1024
+ return cls(traits={"age": 22, "hair": f"brown{addition}", "height": 5.5})
339
1025
 
340
1026
  def code(self) -> str:
341
- """Returns the code for the agent."""
342
- return f"Agent(traits={self.traits})"
1027
+ """Return the code for the agent.
1028
+
1029
+ Example usage:
1030
+
1031
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
1032
+ >>> print(a.code())
1033
+ from edsl.agents.Agent import Agent
1034
+ agent = Agent(traits={'age': 10, 'hair': 'brown', 'height': 5.5})
1035
+ """
1036
+ return (
1037
+ f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
1038
+ )
343
1039
 
344
1040
 
345
1041
  def main():
346
- """Consumes API credits"""
1042
+ """
1043
+ Give an example of usage.
1044
+
1045
+ WARNING: Consume API credits
1046
+ """
347
1047
  from edsl.agents import Agent
348
1048
  from edsl.questions import QuestionMultipleChoice
349
1049
 
@@ -361,13 +1061,11 @@ def main():
361
1061
  question_options=["Yes", "No"],
362
1062
  question_name="food_preference",
363
1063
  )
364
- job = agent.to(question)
365
- # run the job
1064
+ job = question.by(agent)
366
1065
  results = job.run()
367
- # results
368
1066
 
369
1067
 
370
1068
  if __name__ == "__main__":
371
1069
  import doctest
372
1070
 
373
- doctest.testmod()
1071
+ doctest.testmod(optionflags=doctest.ELLIPSIS)