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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. edsl/__init__.py +44 -39
  2. edsl/__version__.py +1 -1
  3. edsl/agents/__init__.py +4 -2
  4. edsl/agents/{Agent.py → agent.py} +442 -152
  5. edsl/agents/{AgentList.py → agent_list.py} +220 -162
  6. edsl/agents/descriptors.py +46 -7
  7. edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
  8. edsl/base/__init__.py +75 -0
  9. edsl/base/base_class.py +1303 -0
  10. edsl/base/data_transfer_models.py +114 -0
  11. edsl/base/enums.py +215 -0
  12. edsl/base.py +8 -0
  13. edsl/buckets/__init__.py +25 -0
  14. edsl/buckets/bucket_collection.py +324 -0
  15. edsl/buckets/model_buckets.py +206 -0
  16. edsl/buckets/token_bucket.py +502 -0
  17. edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
  18. edsl/buckets/token_bucket_client.py +509 -0
  19. edsl/caching/__init__.py +20 -0
  20. edsl/caching/cache.py +814 -0
  21. edsl/caching/cache_entry.py +427 -0
  22. edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
  23. edsl/caching/exceptions.py +24 -0
  24. edsl/caching/orm.py +30 -0
  25. edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
  26. edsl/caching/sql_dict.py +441 -0
  27. edsl/config/__init__.py +8 -0
  28. edsl/config/config_class.py +177 -0
  29. edsl/config.py +4 -176
  30. edsl/conversation/Conversation.py +7 -7
  31. edsl/conversation/car_buying.py +4 -4
  32. edsl/conversation/chips.py +6 -6
  33. edsl/coop/__init__.py +25 -2
  34. edsl/coop/coop.py +311 -75
  35. edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
  36. edsl/coop/exceptions.py +62 -0
  37. edsl/coop/price_fetcher.py +126 -0
  38. edsl/coop/utils.py +89 -24
  39. edsl/data_transfer_models.py +5 -72
  40. edsl/dataset/__init__.py +10 -0
  41. edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
  42. edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
  43. edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
  44. edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
  45. edsl/{results → dataset/display}/table_renderers.py +58 -2
  46. edsl/{results → dataset}/file_exports.py +4 -5
  47. edsl/{results → dataset}/smart_objects.py +2 -2
  48. edsl/enums.py +5 -205
  49. edsl/inference_services/__init__.py +5 -0
  50. edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
  51. edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
  52. edsl/inference_services/data_structures.py +3 -2
  53. edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
  54. edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
  55. edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
  56. edsl/inference_services/registry.py +4 -41
  57. edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
  58. edsl/inference_services/services/__init__.py +31 -0
  59. edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
  60. edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
  61. edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
  62. edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
  63. edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
  64. edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
  65. edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
  66. edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
  67. edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
  68. edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
  69. edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
  70. edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
  71. edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
  72. edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
  73. edsl/inference_services/write_available.py +1 -2
  74. edsl/instructions/__init__.py +6 -0
  75. edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
  76. edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
  77. edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
  78. edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
  79. edsl/interviews/__init__.py +4 -0
  80. edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
  81. edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
  82. edsl/interviews/interview.py +638 -0
  83. edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
  84. edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
  85. edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
  86. edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
  87. edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
  88. edsl/invigilators/__init__.py +38 -0
  89. edsl/invigilators/invigilator_base.py +477 -0
  90. edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
  91. edsl/invigilators/prompt_constructor.py +476 -0
  92. edsl/{agents → invigilators}/prompt_helpers.py +2 -1
  93. edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
  94. edsl/{agents → invigilators}/question_option_processor.py +96 -21
  95. edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
  96. edsl/jobs/__init__.py +7 -1
  97. edsl/jobs/async_interview_runner.py +99 -35
  98. edsl/jobs/check_survey_scenario_compatibility.py +7 -5
  99. edsl/jobs/data_structures.py +153 -22
  100. edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
  101. edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
  102. edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
  103. edsl/jobs/{Jobs.py → jobs.py} +313 -167
  104. edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
  105. edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
  106. edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
  107. edsl/jobs/jobs_pricing_estimation.py +347 -0
  108. edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
  109. edsl/jobs/jobs_runner_asyncio.py +282 -0
  110. edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
  111. edsl/jobs/results_exceptions_handler.py +2 -2
  112. edsl/key_management/__init__.py +28 -0
  113. edsl/key_management/key_lookup.py +161 -0
  114. edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
  115. edsl/key_management/key_lookup_collection.py +82 -0
  116. edsl/key_management/models.py +218 -0
  117. edsl/language_models/__init__.py +7 -2
  118. edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
  119. edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
  120. edsl/language_models/language_model.py +1080 -0
  121. edsl/language_models/model.py +10 -25
  122. edsl/language_models/{ModelList.py → model_list.py} +9 -14
  123. edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
  124. edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
  125. edsl/language_models/repair.py +4 -4
  126. edsl/language_models/utilities.py +4 -4
  127. edsl/notebooks/__init__.py +3 -1
  128. edsl/notebooks/{Notebook.py → notebook.py} +7 -8
  129. edsl/prompts/__init__.py +1 -1
  130. edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
  131. edsl/prompts/{Prompt.py → prompt.py} +101 -95
  132. edsl/questions/HTMLQuestion.py +1 -1
  133. edsl/questions/__init__.py +154 -25
  134. edsl/questions/answer_validator_mixin.py +1 -1
  135. edsl/questions/compose_questions.py +4 -3
  136. edsl/questions/derived/question_likert_five.py +166 -0
  137. edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
  138. edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
  139. edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
  140. edsl/questions/descriptors.py +24 -30
  141. edsl/questions/loop_processor.py +65 -19
  142. edsl/questions/question_base.py +881 -0
  143. edsl/questions/question_base_gen_mixin.py +15 -16
  144. edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
  145. edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
  146. edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
  147. edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
  148. edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
  149. edsl/questions/question_free_text.py +282 -0
  150. edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
  151. edsl/questions/{QuestionList.py → question_list.py} +6 -7
  152. edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
  153. edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
  154. edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
  155. edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
  156. edsl/questions/question_registry.py +4 -9
  157. edsl/questions/register_questions_meta.py +8 -4
  158. edsl/questions/response_validator_abc.py +17 -16
  159. edsl/results/__init__.py +4 -1
  160. edsl/{exceptions/results.py → results/exceptions.py} +1 -1
  161. edsl/results/report.py +197 -0
  162. edsl/results/{Result.py → result.py} +131 -45
  163. edsl/results/{Results.py → results.py} +365 -220
  164. edsl/results/results_selector.py +344 -25
  165. edsl/scenarios/__init__.py +30 -3
  166. edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
  167. edsl/scenarios/directory_scanner.py +156 -13
  168. edsl/scenarios/document_chunker.py +186 -0
  169. edsl/scenarios/exceptions.py +101 -0
  170. edsl/scenarios/file_methods.py +2 -3
  171. edsl/scenarios/{FileStore.py → file_store.py} +275 -189
  172. edsl/scenarios/handlers/__init__.py +14 -14
  173. edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
  174. edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
  175. edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
  176. edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
  177. edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
  178. edsl/scenarios/handlers/latex_file_store.py +5 -0
  179. edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
  180. edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
  181. edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
  182. edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
  183. edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
  184. edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
  185. edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
  186. edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
  187. edsl/scenarios/scenario.py +928 -0
  188. edsl/scenarios/scenario_join.py +18 -5
  189. edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
  190. edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
  191. edsl/scenarios/scenario_selector.py +5 -1
  192. edsl/study/ObjectEntry.py +2 -2
  193. edsl/study/SnapShot.py +5 -5
  194. edsl/study/Study.py +18 -19
  195. edsl/study/__init__.py +6 -4
  196. edsl/surveys/__init__.py +7 -4
  197. edsl/surveys/dag/__init__.py +2 -0
  198. edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
  199. edsl/surveys/{DAG.py → dag/dag.py} +13 -10
  200. edsl/surveys/descriptors.py +1 -1
  201. edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
  202. edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
  203. edsl/surveys/memory/__init__.py +3 -0
  204. edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
  205. edsl/surveys/rules/__init__.py +3 -0
  206. edsl/surveys/{Rule.py → rules/rule.py} +103 -43
  207. edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
  208. edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
  209. edsl/surveys/survey.py +1743 -0
  210. edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
  211. edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
  212. edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
  213. edsl/tasks/__init__.py +32 -0
  214. edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
  215. edsl/tasks/task_creators.py +135 -0
  216. edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
  217. edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
  218. edsl/tasks/task_status_log.py +85 -0
  219. edsl/tokens/__init__.py +2 -0
  220. edsl/tokens/interview_token_usage.py +53 -0
  221. edsl/utilities/PrettyList.py +1 -1
  222. edsl/utilities/SystemInfo.py +25 -22
  223. edsl/utilities/__init__.py +29 -21
  224. edsl/utilities/gcp_bucket/__init__.py +2 -0
  225. edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
  226. edsl/utilities/interface.py +44 -536
  227. edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
  228. edsl/utilities/repair_functions.py +1 -1
  229. {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/METADATA +1 -1
  230. edsl-0.1.49.dist-info/RECORD +347 -0
  231. edsl/Base.py +0 -493
  232. edsl/BaseDiff.py +0 -260
  233. edsl/agents/InvigilatorBase.py +0 -260
  234. edsl/agents/PromptConstructor.py +0 -318
  235. edsl/coop/PriceFetcher.py +0 -54
  236. edsl/data/Cache.py +0 -582
  237. edsl/data/CacheEntry.py +0 -238
  238. edsl/data/SQLiteDict.py +0 -292
  239. edsl/data/__init__.py +0 -5
  240. edsl/data/orm.py +0 -10
  241. edsl/exceptions/cache.py +0 -5
  242. edsl/exceptions/coop.py +0 -14
  243. edsl/exceptions/data.py +0 -14
  244. edsl/exceptions/scenarios.py +0 -29
  245. edsl/jobs/Answers.py +0 -43
  246. edsl/jobs/JobsPrompts.py +0 -354
  247. edsl/jobs/buckets/BucketCollection.py +0 -134
  248. edsl/jobs/buckets/ModelBuckets.py +0 -65
  249. edsl/jobs/buckets/TokenBucket.py +0 -283
  250. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  251. edsl/jobs/interviews/Interview.py +0 -395
  252. edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
  253. edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
  254. edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
  255. edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
  256. edsl/jobs/tasks/TaskCreators.py +0 -64
  257. edsl/jobs/tasks/TaskStatusLog.py +0 -23
  258. edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
  259. edsl/language_models/LanguageModel.py +0 -635
  260. edsl/language_models/ServiceDataSources.py +0 -0
  261. edsl/language_models/key_management/KeyLookup.py +0 -63
  262. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  263. edsl/language_models/key_management/models.py +0 -137
  264. edsl/questions/QuestionBase.py +0 -544
  265. edsl/questions/QuestionFreeText.py +0 -130
  266. edsl/questions/derived/QuestionLikertFive.py +0 -76
  267. edsl/results/ResultsExportMixin.py +0 -45
  268. edsl/results/TextEditor.py +0 -50
  269. edsl/results/results_fetch_mixin.py +0 -33
  270. edsl/results/results_tools_mixin.py +0 -98
  271. edsl/scenarios/DocumentChunker.py +0 -104
  272. edsl/scenarios/Scenario.py +0 -548
  273. edsl/scenarios/ScenarioHtmlMixin.py +0 -65
  274. edsl/scenarios/ScenarioListExportMixin.py +0 -45
  275. edsl/scenarios/handlers/latex.py +0 -5
  276. edsl/shared.py +0 -1
  277. edsl/surveys/Survey.py +0 -1301
  278. edsl/surveys/SurveyQualtricsImport.py +0 -284
  279. edsl/surveys/SurveyToApp.py +0 -141
  280. edsl/surveys/instructions/__init__.py +0 -0
  281. edsl/tools/__init__.py +0 -1
  282. edsl/tools/clusters.py +0 -192
  283. edsl/tools/embeddings.py +0 -27
  284. edsl/tools/embeddings_plotting.py +0 -118
  285. edsl/tools/plotting.py +0 -112
  286. edsl/tools/summarize.py +0 -18
  287. edsl/utilities/data/Registry.py +0 -6
  288. edsl/utilities/data/__init__.py +0 -1
  289. edsl/utilities/data/scooter_results.json +0 -1
  290. edsl-0.1.47.dist-info/RECORD +0 -354
  291. /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
  292. /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
  293. /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
  294. /edsl/{results → dataset/display}/table_data_class.py +0 -0
  295. /edsl/{results → dataset/display}/table_display.css +0 -0
  296. /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
  297. /edsl/{results → dataset}/tree_explore.py +0 -0
  298. /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
  299. /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
  300. /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
  301. /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
  302. /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
  303. /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
  304. /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
  305. /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
  306. /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
  307. /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
  308. /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
  309. /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
  310. /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
  311. /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
  312. /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
  313. {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/LICENSE +0 -0
  314. {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/WHEEL +0 -0
@@ -1,9 +1,53 @@
1
- """An Agent is an AI agent that can reference a set of traits in answering questions."""
1
+ """Agent module for creating and managing AI agents with traits and question-answering capabilities.
2
+
3
+ This module provides the Agent class, which represents an AI agent with customizable traits,
4
+ instructions, and optional direct question-answering methods. Agents are the primary entities
5
+ that answer questions in the EDSL framework, and they can be configured with:
6
+
7
+ - Traits: Key-value pairs representing agent attributes (age, occupation, preferences, etc.)
8
+ - Instructions: Directives for how the agent should answer questions
9
+ - Codebooks: Human-readable descriptions for traits used in prompts
10
+ - Dynamic traits: Functions that can modify traits based on questions
11
+ - Direct answering methods: Functions that can answer specific questions without using an LLM
12
+
13
+ Agents can be combined, modified, and used to answer various question types through different
14
+ invigilator mechanisms.
15
+
16
+ Codebook and Trait Rendering
17
+ ---------------------------
18
+ One of the key features of the Agent class is its ability to use codebooks to improve
19
+ how traits are presented to language models. A codebook is a dictionary that maps trait keys
20
+ to human-readable descriptions:
21
+
22
+ ```python
23
+ traits = {"age": 30, "occupation": "doctor"}
24
+ codebook = {"age": "Age in years", "occupation": "Current profession"}
25
+ agent = Agent(traits=traits, codebook=codebook)
26
+ ```
27
+
28
+ When an agent with a codebook generates prompts, it will use these descriptions
29
+ instead of the raw trait keys, creating more natural and descriptive prompts:
30
+
31
+ Without codebook: "Your traits: {'age': 30, 'occupation': 'doctor'}"
32
+ With codebook:
33
+ ```
34
+ Your traits:
35
+ Age in years: 30
36
+ Current profession: doctor
37
+ ```
38
+
39
+ This approach helps language models better understand the traits and can lead to
40
+ more natural responses that properly interpret the agent's characteristics.
41
+ """
2
42
 
3
43
  from __future__ import annotations
4
44
  import copy
5
45
  import inspect
6
46
  import types
47
+ import warnings
48
+ from uuid import uuid4
49
+ from contextlib import contextmanager
50
+
7
51
  from typing import (
8
52
  Callable,
9
53
  Optional,
@@ -14,23 +58,21 @@ from typing import (
14
58
  runtime_checkable,
15
59
  TypeVar,
16
60
  )
17
- from contextlib import contextmanager
18
- from dataclasses import dataclass
19
61
 
20
62
  # Type variable for the Agent class
21
63
  A = TypeVar("A", bound="Agent")
22
64
 
23
65
  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
66
+ from ..caching import Cache
67
+ from ..surveys import Survey
68
+ from ..scenarios import Scenario
69
+ from ..language_models import LanguageModel
70
+ from ..surveys.memory import MemoryPlan
71
+ from ..questions import QuestionBase
72
+ from ..invigilators import InvigilatorBase
73
+ from ..prompts import Prompt
74
+ from ..questions import QuestionBase
75
+ from ..key_management import KeyLookup
34
76
 
35
77
 
36
78
  @runtime_checkable
@@ -40,52 +82,92 @@ class DirectAnswerMethod(Protocol):
40
82
  def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
41
83
 
42
84
 
43
- from uuid import uuid4
85
+ from ..base import Base
86
+ from ..scenarios import Scenario
87
+ from ..questions import QuestionScenarioRenderError
88
+ from ..data_transfer_models import AgentResponseDict
89
+ from ..utilities import (
90
+ sync_wrapper,
91
+ create_restricted_function,
92
+ dict_hash,
93
+ remove_edsl_version,
94
+ )
44
95
 
45
- from edsl.Base import Base
46
- from edsl.exceptions.questions import QuestionScenarioRenderError
47
96
 
48
- from edsl.exceptions.agents import (
97
+ from .exceptions import (
49
98
  AgentErrors,
50
99
  AgentCombinationError,
51
100
  AgentDirectAnswerFunctionError,
52
101
  AgentDynamicTraitsFunctionError,
53
102
  )
54
103
 
55
- from edsl.agents.descriptors import (
104
+ from .descriptors import (
56
105
  TraitsDescriptor,
57
106
  CodebookDescriptor,
58
107
  InstructionDescriptor,
59
108
  NameDescriptor,
60
109
  )
61
- from edsl.utilities.decorators import (
62
- sync_wrapper,
63
- )
64
- from edsl.utilities.remove_edsl_version import remove_edsl_version
65
- from edsl.data_transfer_models import AgentResponseDict
66
- from edsl.utilities.restricted_python import create_restricted_function
67
-
68
- from edsl.scenarios.Scenario import Scenario
69
110
 
70
111
 
71
112
  class AgentTraits(Scenario):
72
- """A class representing the traits of an agent."""
113
+ """A class representing the traits of an agent.
114
+
115
+ AgentTraits inherits from Scenario to provide a structured way to store and
116
+ access agent characteristics. This allows agent traits to be handled consistently
117
+ throughout the EDSL framework, including for presentation in prompts.
118
+
119
+ Attributes:
120
+ data: Dictionary containing the agent's traits as key-value pairs
121
+ """
73
122
 
74
123
  def __repr__(self):
124
+ """Generate a string representation of the agent traits.
125
+
126
+ Returns:
127
+ str: String representation of the agent traits dictionary
128
+ """
75
129
  return f"{self.data}"
76
130
 
77
131
 
78
132
  class Agent(Base):
79
- """An class representing an agent that can answer questions."""
133
+ """A class representing an AI agent with customizable traits that can answer questions.
134
+
135
+ An Agent in EDSL represents an entity with specific characteristics (traits) that can
136
+ answer questions through various mechanisms. Agents can use language models to generate
137
+ responses based on their traits, directly answer questions through custom functions, or
138
+ dynamically adjust their traits based on the questions being asked.
139
+
140
+ Key capabilities:
141
+ - Store and manage agent characteristics (traits)
142
+ - Provide instructions on how the agent should answer
143
+ - Support for custom codebooks to provide human-readable trait descriptions
144
+ - Integration with multiple question types and language models
145
+ - Combine agents to create more complex personas
146
+ - Customize agent behavior through direct answering methods
147
+
148
+ Agents are used in conjunction with Questions, Scenarios, and Surveys to create
149
+ structured interactions with language models.
150
+ """
80
151
 
81
152
  __documentation__ = "https://docs.expectedparrot.com/en/latest/agents.html"
82
153
 
83
154
  default_instruction = """You are answering questions as if you were a human. Do not break character."""
84
155
 
156
+ # Trait storage using descriptors for validation and management
85
157
  _traits = TraitsDescriptor()
158
+
159
+ # Codebook maps trait keys to human-readable descriptions
160
+ # This improves prompt readability and LLM understanding
161
+ # Example: {'age': 'Age in years'} → "Age in years: 30" instead of "age: 30"
86
162
  codebook = CodebookDescriptor()
163
+
164
+ # Instructions for how the agent should answer questions
87
165
  instruction = InstructionDescriptor()
166
+
167
+ # Optional name identifier for the agent
88
168
  name = NameDescriptor()
169
+
170
+ # Default values for function-related attributes
89
171
  dynamic_traits_function_name = ""
90
172
  answer_question_directly_function_name = ""
91
173
  has_dynamic_traits_function = False
@@ -103,57 +185,67 @@ class Agent(Base):
103
185
  answer_question_directly_source_code: Optional[str] = None,
104
186
  answer_question_directly_function_name: Optional[str] = None,
105
187
  ):
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})
188
+ """Initialize a new Agent instance with specified traits and capabilities.
189
+
190
+ Args:
191
+ traits: Dictionary of agent characteristics (e.g., {"age": 30, "occupation": "doctor"})
192
+ name: Optional name identifier for the agent
193
+ codebook: Dictionary mapping trait keys to human-readable descriptions for prompts.
194
+ This provides more descriptive labels for traits when rendering prompts.
195
+ For example, {'age': 'Age in years'} would display "Age in years: 30" instead of "age: 30".
196
+ instruction: Directive for how the agent should answer questions
197
+ traits_presentation_template: Jinja2 template for formatting traits in prompts
198
+ dynamic_traits_function: Function that can modify traits based on questions
199
+ dynamic_traits_function_source_code: Source code string for the dynamic traits function
200
+ dynamic_traits_function_name: Name of the dynamic traits function
201
+ answer_question_directly_source_code: Source code for direct question answering method
202
+ answer_question_directly_function_name: Name of the direct answering function
203
+
204
+ The Agent class brings together several key concepts:
205
+
206
+ Traits
207
+ ------
208
+ Traits are key-value pairs that define an agent's characteristics. These are used
209
+ to construct a prompt that guides the language model on how to respond.
210
+
211
+ Example:
212
+ >>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
126
213
  >>> a.traits
127
214
  {'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.")
215
+
216
+ Traits Presentation
217
+ ------------------
218
+ The traits_presentation_template controls how traits are formatted in prompts.
219
+ It uses Jinja2 templating to insert trait values.
220
+
221
+ Example:
222
+ >>> a = Agent(traits={"age": 10}, traits_presentation_template="I am a {{age}} year old.")
134
223
  >>> repr(a.agent_persona)
135
224
  '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
-
225
+
226
+ Codebooks
227
+ ---------
228
+ Codebooks provide human-readable descriptions for traits in prompts.
229
+
230
+ Example:
141
231
  >>> traits = {"age": 10, "hair": "brown", "height": 5.5}
142
232
  >>> codebook = {'age': 'Their age is'}
143
- >>> a = Agent(traits = traits, codebook = codebook, traits_presentation_template = "This agent is Dave. {{codebook['age']}} {{age}}")
233
+ >>> a = Agent(traits=traits, codebook=codebook,
234
+ ... traits_presentation_template="This agent is Dave. {{codebook['age']}} {{age}}")
144
235
  >>> d = a.traits | {'codebook': a.codebook}
145
236
  >>> a.agent_persona.render(d)
146
237
  Prompt(text=\"""This agent is Dave. Their age is 10\""")
147
-
238
+
148
239
  Instructions
149
240
  ------------
150
- The agent can also have instructions. These are instructions that are given to the agent when answering questions.
151
-
241
+ Instructions guide how the agent should answer questions. If not provided,
242
+ a default instruction is used.
243
+
152
244
  >>> Agent.default_instruction
153
245
  '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
-
246
+
247
+ For details on how these components are used to construct prompts, see
248
+ :py:class:`edsl.agents.Invigilator.InvigilatorBase`.
157
249
  """
158
250
  self._initialize_basic_attributes(traits, name, codebook)
159
251
  self._initialize_instruction(instruction)
@@ -170,13 +262,25 @@ class Agent(Base):
170
262
  self.current_question = None
171
263
 
172
264
  def _initialize_basic_attributes(self, traits, name, codebook) -> None:
173
- """Initialize the basic attributes of the agent."""
265
+ """Initialize the basic attributes of the agent.
266
+
267
+ Args:
268
+ traits: Dictionary of agent characteristics
269
+ name: Name identifier for the agent
270
+ codebook: Dictionary mapping trait keys to descriptions
271
+ """
174
272
  self.name = name
175
273
  self._traits = AgentTraits(traits or dict())
176
274
  self.codebook = codebook or dict()
177
275
 
178
276
  def _initialize_instruction(self, instruction) -> None:
179
- """Initialize the instruction for the agent i.e., how the agent should answer questions."""
277
+ """Initialize the instruction for how the agent should answer questions.
278
+
279
+ If no instruction is provided, uses the default instruction.
280
+
281
+ Args:
282
+ instruction: Directive for how the agent should answer questions
283
+ """
180
284
  if instruction is None:
181
285
  self.instruction = self.default_instruction
182
286
  self._instruction = self.default_instruction
@@ -189,13 +293,71 @@ class Agent(Base):
189
293
  def _initialize_traits_presentation_template(
190
294
  self, traits_presentation_template
191
295
  ) -> None:
192
- """Initialize the traits presentation template. How the agent's traits are presented to the LLM."""
296
+ """Initialize the template for presenting agent traits in prompts.
297
+
298
+ This method sets up how the agent's traits will be formatted in language model prompts.
299
+ The template is a Jinja2 template string that can reference trait values and other
300
+ agent properties.
301
+
302
+ If no template is provided:
303
+ - If a codebook exists, the method creates a template that displays each trait with its
304
+ codebook description instead of the raw key names (e.g., "Age in years: 30" instead of "age: 30")
305
+ - Without a codebook, it uses a default template that displays all traits as a dictionary
306
+
307
+ Custom templates always take precedence over automatically generated ones, giving users
308
+ complete control over how traits are presented.
309
+
310
+ Args:
311
+ traits_presentation_template: Optional Jinja2 template string for formatting traits.
312
+ If not provided, a default template will be generated.
313
+
314
+ Examples:
315
+ With no template or codebook, traits are shown as a dictionary:
316
+
317
+ >>> agent = Agent(traits={"age": 25, "occupation": "engineer"})
318
+ >>> str(agent.prompt().text)
319
+ 'Your traits: {\'age\': 25, \'occupation\': \'engineer\'}'
320
+
321
+ With a codebook but no custom template, traits are shown with descriptions:
322
+
323
+ >>> codebook = {"age": "Age in years", "occupation": "Current profession"}
324
+ >>> agent = Agent(traits={"age": 25, "occupation": "engineer"}, codebook=codebook)
325
+ >>> print(agent.prompt().text) # doctest: +NORMALIZE_WHITESPACE
326
+ Your traits:
327
+ Age in years: 25
328
+ Current profession: engineer
329
+
330
+ With a custom template, that format is used regardless of codebook:
331
+
332
+ >>> template = "Person: {{age}} years old, works as {{occupation}}"
333
+ >>> agent = Agent(traits={"age": 25, "occupation": "engineer"},
334
+ ... codebook=codebook, traits_presentation_template=template)
335
+ >>> str(agent.prompt().text)
336
+ 'Person: 25 years old, works as engineer'
337
+ """
193
338
  if traits_presentation_template is not None:
194
339
  self._traits_presentation_template = traits_presentation_template
195
340
  self.traits_presentation_template = traits_presentation_template
196
341
  self.set_traits_presentation_template = True
197
342
  else:
198
- self.traits_presentation_template = "Your traits: {{traits}}"
343
+ # Set the default template based on whether a codebook exists
344
+ if self.codebook:
345
+ # Create a template that uses the codebook descriptions
346
+ traits_lines = []
347
+ for trait_key in self.traits.keys():
348
+ if trait_key in self.codebook:
349
+ # Use codebook description if available
350
+ traits_lines.append(f"{self.codebook[trait_key]}: {{{{ {trait_key} }}}}")
351
+ else:
352
+ # Fall back to raw key for traits without codebook entries
353
+ traits_lines.append(f"{trait_key}: {{{{ {trait_key} }}}}")
354
+
355
+ # Join all trait lines with newlines
356
+ self.traits_presentation_template = "Your traits:\n" + "\n".join(traits_lines)
357
+ else:
358
+ # Use the standard dictionary format if no codebook
359
+ self.traits_presentation_template = "Your traits: {{traits}}"
360
+
199
361
  self.set_traits_presentation_template = False
200
362
 
201
363
  def _initialize_dynamic_traits_function(
@@ -204,7 +366,17 @@ class Agent(Base):
204
366
  dynamic_traits_function_source_code,
205
367
  dynamic_traits_function_name,
206
368
  ) -> None:
207
- """Initialize the dynamic traits function i.e., a function that returns a dictionary of traits based on the question."""
369
+ """Initialize a function that can dynamically modify agent traits based on questions.
370
+
371
+ This allows traits to change based on the question being asked, enabling
372
+ more sophisticated agent behaviors. The function can be provided directly
373
+ or as source code that will be compiled.
374
+
375
+ Args:
376
+ dynamic_traits_function: Function object that returns a dictionary of traits
377
+ dynamic_traits_function_source_code: Source code string for the function
378
+ dynamic_traits_function_name: Name to assign to the function
379
+ """
208
380
  # Deal with dynamic traits function
209
381
  self.dynamic_traits_function = dynamic_traits_function
210
382
 
@@ -225,6 +397,16 @@ class Agent(Base):
225
397
  answer_question_directly_source_code,
226
398
  answer_question_directly_function_name,
227
399
  ) -> None:
400
+ """Initialize a method for the agent to directly answer questions without using an LLM.
401
+
402
+ This allows creating agents that answer programmatically rather than through
403
+ language model generation. The direct answering method can be provided as
404
+ source code that will be compiled and bound to this agent instance.
405
+
406
+ Args:
407
+ answer_question_directly_source_code: Source code for the direct answering method
408
+ answer_question_directly_function_name: Name to assign to the method
409
+ """
228
410
  if answer_question_directly_source_code:
229
411
  self.answer_question_directly_function_name = (
230
412
  answer_question_directly_function_name
@@ -248,58 +430,161 @@ class Agent(Base):
248
430
  self.set_traits_presentation_template = False
249
431
 
250
432
  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!'
433
+ """Create a deep copy of this agent with all its traits and capabilities.
434
+
435
+ This method creates a completely independent copy of the agent, including
436
+ all its traits, codebook, instructions, and special functions like dynamic
437
+ traits and direct answering methods.
438
+
439
+ Returns:
440
+ Agent: A new agent instance that is functionally identical to this one
441
+
442
+ Examples:
443
+ Create a duplicate agent and verify it's equal but not the same object:
444
+
445
+ >>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5},
446
+ ... codebook={'age': 'Their age is'})
447
+ >>> a2 = a.duplicate()
448
+ >>> a2 == a # Functionally equivalent
449
+ True
450
+ >>> id(a) == id(a2) # But different objects
451
+ False
452
+
453
+ Duplicating preserves direct answering methods:
454
+
455
+ >>> def f(self, question, scenario): return "I am a direct answer."
456
+ >>> a.add_direct_question_answering_method(f)
457
+ >>> hasattr(a, "answer_question_directly")
458
+ True
459
+ >>> a2 = a.duplicate()
460
+ >>> a2.answer_question_directly(None, None)
461
+ 'I am a direct answer.'
462
+
463
+ Duplicating preserves custom instructions:
464
+
465
+ >>> a = Agent(traits={'age': 10}, instruction="Have fun!")
466
+ >>> a2 = a.duplicate()
467
+ >>> a2.instruction
468
+ 'Have fun!'
271
469
  """
272
470
  new_agent = Agent.from_dict(self.to_dict())
471
+
472
+ # Transfer direct answering method if present
273
473
  if hasattr(self, "answer_question_directly"):
274
474
  answer_question_directly = self.answer_question_directly
275
475
  newf = lambda self, question, scenario: answer_question_directly(
276
476
  question, scenario
277
477
  )
278
478
  new_agent.add_direct_question_answering_method(newf)
479
+
480
+ # Transfer dynamic traits function if present
279
481
  if hasattr(self, "dynamic_traits_function"):
280
482
  dynamic_traits_function = self.dynamic_traits_function
281
483
  new_agent.dynamic_traits_function = dynamic_traits_function
484
+
282
485
  return new_agent
283
486
 
284
487
  @property
285
488
  def agent_persona(self) -> Prompt:
286
- """Return the agent persona template."""
287
- from edsl.prompts.Prompt import Prompt
489
+ """Get the agent's persona template as a Prompt object.
490
+
491
+ This property provides access to the template that formats the agent's traits
492
+ for presentation in prompts. The template is wrapped in a Prompt object
493
+ that supports rendering with variable substitution.
494
+
495
+ Returns:
496
+ Prompt: A prompt object containing the traits presentation template
497
+ """
498
+ from ..prompts import Prompt
288
499
 
289
500
  return Prompt(text=self.traits_presentation_template)
290
501
 
291
502
  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}\""")
503
+ """Generate a formatted prompt containing the agent's traits.
504
+
505
+ This method renders the agent's traits presentation template with the
506
+ agent's traits and codebook, creating a formatted prompt that can be
507
+ used in language model requests.
508
+
509
+ The method is dynamic and responsive to changes in the agent's state:
510
+
511
+ 1. If a custom template was explicitly set during initialization, it will be used
512
+ 2. If using the default template and the codebook has been updated since
513
+ initialization, this method will recreate the template to reflect the current
514
+ codebook values
515
+ 3. The template is rendered with access to all trait values, the complete traits
516
+ dictionary, and the codebook
517
+
518
+ The template rendering makes the following variables available:
519
+ - All individual trait keys (e.g., {{age}}, {{occupation}})
520
+ - The full traits dictionary as {{traits}}
521
+ - The codebook as {{codebook}}
522
+
523
+ Returns:
524
+ Prompt: A Prompt object containing the rendered template
525
+
526
+ Raises:
527
+ QuestionScenarioRenderError: If any template variables remain undefined
528
+
529
+ Examples:
530
+ Basic trait rendering without a codebook:
531
+
532
+ >>> agent = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
533
+ >>> agent.prompt()
534
+ Prompt(text=\"""Your traits: {'age': 10, 'hair': 'brown', 'height': 5.5}\""")
535
+
536
+ Trait rendering with a codebook (more readable format):
537
+
538
+ >>> codebook = {"age": "Age in years", "hair": "Hair color"}
539
+ >>> agent = Agent(traits={"age": 10, "hair": "brown"}, codebook=codebook)
540
+ >>> print(agent.prompt().text) # doctest: +NORMALIZE_WHITESPACE
541
+ Your traits:
542
+ Age in years: 10
543
+ Hair color: brown
544
+
545
+ Adding a codebook after initialization updates the rendering:
546
+
547
+ >>> agent = Agent(traits={"age": 30, "occupation": "doctor"})
548
+ >>> initial_prompt = agent.prompt()
549
+ >>> "Your traits: {" in initial_prompt.text
550
+ True
551
+ >>> agent.codebook = {"age": "Age", "occupation": "Profession"}
552
+ >>> updated_prompt = agent.prompt()
553
+ >>> "Age: 30" in updated_prompt.text
554
+ True
555
+ >>> "Profession: doctor" in updated_prompt.text
556
+ True
557
+
558
+ Custom templates can reference any trait directly:
559
+
560
+ >>> template = "Profile: {{age}} year old {{occupation}}"
561
+ >>> agent = Agent(traits={"age": 45, "occupation": "teacher"},
562
+ ... traits_presentation_template=template)
563
+ >>> agent.prompt().text
564
+ 'Profile: 45 year old teacher'
299
565
  """
566
+ # If using the default template and the codebook has been updated since initialization,
567
+ # recreate the template to use the current codebook
568
+ if not self.set_traits_presentation_template and self.codebook:
569
+ # Create a template that uses the codebook descriptions
570
+ traits_lines = []
571
+ for trait_key in self.traits.keys():
572
+ if trait_key in self.codebook:
573
+ # Use codebook description if available
574
+ traits_lines.append(f"{self.codebook[trait_key]}: {{{{ {trait_key} }}}}")
575
+ else:
576
+ # Fall back to raw key for traits without codebook entries
577
+ traits_lines.append(f"{trait_key}: {{{{ {trait_key} }}}}")
578
+
579
+ # Join all trait lines with newlines
580
+ self.traits_presentation_template = "Your traits:\n" + "\n".join(traits_lines)
581
+
582
+ # Create a dictionary with traits, a reference to all traits, and the codebook
300
583
  replacement_dict = (
301
584
  self.traits | {"traits": self.traits} | {"codebook": self.codebook}
302
585
  )
586
+
587
+ # Check for any undefined variables in the template
303
588
  if undefined := self.agent_persona.undefined_template_variables(
304
589
  replacement_dict
305
590
  ):
@@ -310,28 +595,41 @@ class Agent(Base):
310
595
  return self.agent_persona.render(replacement_dict)
311
596
 
312
597
  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: ...
598
+ """Validate that the dynamic traits function has the correct signature.
599
+
600
+ This method checks if the dynamic traits function (if present) has the correct
601
+ parameter list. The function should either take no parameters or a single
602
+ parameter named 'question'.
603
+
604
+ Raises:
605
+ AgentDynamicTraitsFunctionError: If the function signature is invalid
606
+
607
+ Examples:
608
+ Valid function with 'question' parameter:
609
+
610
+ >>> def f(question): return {"age": 10, "hair": "brown", "height": 5.5}
611
+ >>> a = Agent(dynamic_traits_function=f)
612
+ >>> a._check_dynamic_traits_function()
613
+
614
+ Invalid function with extra parameters:
615
+
616
+ >>> def g(question, poo): return {"age": 10, "hair": "brown", "height": 5.5}
617
+ >>> a = Agent(dynamic_traits_function=g)
618
+ Traceback (most recent call last):
619
+ ...
620
+ edsl.agents.exceptions.AgentDynamicTraitsFunctionError: ...
326
621
  """
327
622
  if self.has_dynamic_traits_function:
328
623
  sig = inspect.signature(self.dynamic_traits_function)
624
+
329
625
  if "question" in sig.parameters:
626
+ # If it has 'question' parameter, it should be the only one
330
627
  if len(sig.parameters) > 1:
331
628
  raise AgentDynamicTraitsFunctionError(
332
629
  message=f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
333
630
  )
334
631
  else:
632
+ # If it doesn't have 'question', it shouldn't have any parameters
335
633
  if len(sig.parameters) > 0:
336
634
  raise AgentDynamicTraitsFunctionError(
337
635
  f"""The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should have no parameters or
@@ -340,27 +638,33 @@ class Agent(Base):
340
638
 
341
639
  @property
342
640
  def traits(self) -> dict[str, str]:
343
- """An agent's traits, which is a dictionary.
344
-
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.
349
-
350
- Example:
351
-
352
- >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
353
- >>> a.traits
354
- {'age': 10, 'hair': 'brown', 'height': 5.5}
355
-
641
+ """Get the agent's traits, potentially using dynamic generation.
642
+
643
+ This property provides access to the agent's traits, either from the stored
644
+ traits dictionary or by calling a dynamic traits function if one is defined.
645
+ If a dynamic traits function is used, it may take the current question as a
646
+ parameter to generate context-aware traits.
647
+
648
+ Returns:
649
+ dict: Dictionary of agent traits (key-value pairs)
650
+
651
+ Examples:
652
+ >>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
653
+ >>> a.traits
654
+ {'age': 10, 'hair': 'brown', 'height': 5.5}
356
655
  """
357
656
  if self.has_dynamic_traits_function:
657
+ # Check if the function expects a question parameter
358
658
  sig = inspect.signature(self.dynamic_traits_function)
659
+
359
660
  if "question" in sig.parameters:
661
+ # Call with the current question
360
662
  return self.dynamic_traits_function(question=self.current_question)
361
663
  else:
664
+ # Call without parameters
362
665
  return self.dynamic_traits_function()
363
666
  else:
667
+ # Return the stored traits
364
668
  return dict(self._traits)
365
669
 
366
670
  @contextmanager
@@ -383,8 +687,6 @@ class Agent(Base):
383
687
  def traits(self, traits: dict[str, str]):
384
688
  with self.modify_traits_context():
385
689
  self._traits = traits
386
- # self._check_before_modifying_traits()
387
- # self._traits = AgentTraits(traits)
388
690
 
389
691
  def rename(
390
692
  self,
@@ -516,7 +818,6 @@ class Agent(Base):
516
818
  'I am a direct answer.'
517
819
  """
518
820
  if hasattr(self, "answer_question_directly"):
519
- import warnings
520
821
 
521
822
  warnings.warn(
522
823
  "Warning: overwriting existing answer_question_directly method"
@@ -525,12 +826,6 @@ class Agent(Base):
525
826
  self.validate_response = validate_response
526
827
  self.translate_response = translate_response
527
828
 
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
- # )
533
-
534
829
  signature = inspect.signature(method)
535
830
  for argument in ["question", "scenario", "self"]:
536
831
  if argument not in signature.parameters:
@@ -568,9 +863,8 @@ class Agent(Base):
568
863
  An invigator is an object that is responsible for administering a question to an agent and
569
864
  recording the responses.
570
865
  """
571
- from edsl.language_models.model import Model
572
-
573
- from edsl.scenarios.Scenario import Scenario
866
+ from ..language_models import Model
867
+ from ..scenarios import Scenario
574
868
 
575
869
  cache = cache
576
870
  self.current_question = question
@@ -621,7 +915,7 @@ class Agent(Base):
621
915
 
622
916
  >>> a = Agent(traits = {})
623
917
  >>> a.add_direct_question_answering_method(lambda self, question, scenario: "I am a direct answer.")
624
- >>> from edsl.questions.QuestionFreeText import QuestionFreeText
918
+ >>> from edsl.questions import QuestionFreeText
625
919
  >>> q = QuestionFreeText.example()
626
920
  >>> a.answer_question(question = q, cache = False).answer
627
921
  'I am a direct answer.'
@@ -652,7 +946,7 @@ class Agent(Base):
652
946
  This method returns the invigilator class that should be used to answer a question.
653
947
  The invigilator class is determined by the type of question and the type of agent.
654
948
  """
655
- from edsl.agents.Invigilator import (
949
+ from ..invigilators import (
656
950
  InvigilatorHuman,
657
951
  InvigilatorFunctional,
658
952
  InvigilatorAI,
@@ -679,14 +973,14 @@ class Agent(Base):
679
973
  key_lookup: Optional["KeyLookup"] = None,
680
974
  ) -> "InvigilatorBase":
681
975
  """Create an Invigilator."""
682
- from edsl.language_models.model import Model
683
- from edsl.scenarios.Scenario import Scenario
976
+ from ..language_models import Model
977
+ from ..scenarios import Scenario
684
978
 
685
979
  model = model or Model()
686
980
  scenario = scenario or Scenario()
687
981
 
688
982
  if cache is None:
689
- from edsl.data.Cache import Cache
983
+ from ..caching import Cache
690
984
 
691
985
  cache = Cache()
692
986
 
@@ -755,7 +1049,7 @@ class Agent(Base):
755
1049
  >>> a1 + a1
756
1050
  Traceback (most recent call last):
757
1051
  ...
758
- edsl.exceptions.agents.AgentCombinationError: The agents have overlapping traits: {'age'}.
1052
+ edsl.agents.exceptions.AgentCombinationError: The agents have overlapping traits: {'age'}.
759
1053
  ...
760
1054
  >>> a1 = Agent(traits = {"age": 10}, codebook = {"age": "Their age is"})
761
1055
  >>> a2 = Agent(traits = {"height": 5.5}, codebook = {"height": "Their height is"})
@@ -888,8 +1182,6 @@ class Agent(Base):
888
1182
  >>> hash(Agent.example())
889
1183
  2067581884874391607
890
1184
  """
891
- from edsl.utilities.utilities import dict_hash
892
-
893
1185
  return dict_hash(self.to_dict(add_edsl_version=False))
894
1186
 
895
1187
  def to_dict(self, add_edsl_version=True) -> dict[str, Union[dict, bool]]:
@@ -1030,12 +1322,10 @@ class Agent(Base):
1030
1322
 
1031
1323
  >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
1032
1324
  >>> print(a.code())
1033
- from edsl.agents.Agent import Agent
1325
+ from edsl.agents import Agent
1034
1326
  agent = Agent(traits={'age': 10, 'hair': 'brown', 'height': 5.5})
1035
1327
  """
1036
- return (
1037
- f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
1038
- )
1328
+ return f"from edsl.agents import Agent\nagent = Agent(traits={self.traits})"
1039
1329
 
1040
1330
 
1041
1331
  def main():
@@ -1044,8 +1334,8 @@ def main():
1044
1334
 
1045
1335
  WARNING: Consume API credits
1046
1336
  """
1047
- from edsl.agents import Agent
1048
- from edsl.questions import QuestionMultipleChoice
1337
+ from ..agents import Agent
1338
+ from ..questions import QuestionMultipleChoice
1049
1339
 
1050
1340
  # a simple agent
1051
1341
  agent = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})