edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__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 (344) hide show
  1. edsl/Base.py +413 -332
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +57 -49
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +1071 -867
  7. edsl/agents/AgentList.py +551 -413
  8. edsl/agents/Invigilator.py +284 -233
  9. edsl/agents/InvigilatorBase.py +257 -270
  10. edsl/agents/PromptConstructor.py +272 -354
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/__init__.py +2 -3
  14. edsl/agents/descriptors.py +99 -99
  15. edsl/agents/prompt_helpers.py +129 -129
  16. edsl/agents/question_option_processor.py +172 -0
  17. edsl/auto/AutoStudy.py +130 -117
  18. edsl/auto/StageBase.py +243 -230
  19. edsl/auto/StageGenerateSurvey.py +178 -178
  20. edsl/auto/StageLabelQuestions.py +125 -125
  21. edsl/auto/StagePersona.py +61 -61
  22. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  23. edsl/auto/StagePersonaDimensionValues.py +74 -74
  24. edsl/auto/StagePersonaDimensions.py +69 -69
  25. edsl/auto/StageQuestions.py +74 -73
  26. edsl/auto/SurveyCreatorPipeline.py +21 -21
  27. edsl/auto/utilities.py +218 -224
  28. edsl/base/Base.py +279 -279
  29. edsl/config.py +177 -157
  30. edsl/conversation/Conversation.py +290 -290
  31. edsl/conversation/car_buying.py +59 -58
  32. edsl/conversation/chips.py +95 -95
  33. edsl/conversation/mug_negotiation.py +81 -81
  34. edsl/conversation/next_speaker_utilities.py +93 -93
  35. edsl/coop/CoopFunctionsMixin.py +15 -0
  36. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  37. edsl/coop/PriceFetcher.py +54 -54
  38. edsl/coop/__init__.py +2 -2
  39. edsl/coop/coop.py +1106 -1028
  40. edsl/coop/utils.py +131 -131
  41. edsl/data/Cache.py +573 -555
  42. edsl/data/CacheEntry.py +230 -233
  43. edsl/data/CacheHandler.py +168 -149
  44. edsl/data/RemoteCacheSync.py +186 -78
  45. edsl/data/SQLiteDict.py +292 -292
  46. edsl/data/__init__.py +5 -4
  47. edsl/data/hack.py +10 -0
  48. edsl/data/orm.py +10 -10
  49. edsl/data_transfer_models.py +74 -73
  50. edsl/enums.py +202 -175
  51. edsl/exceptions/BaseException.py +21 -21
  52. edsl/exceptions/__init__.py +54 -54
  53. edsl/exceptions/agents.py +54 -42
  54. edsl/exceptions/cache.py +5 -5
  55. edsl/exceptions/configuration.py +16 -16
  56. edsl/exceptions/coop.py +10 -10
  57. edsl/exceptions/data.py +14 -14
  58. edsl/exceptions/general.py +34 -34
  59. edsl/exceptions/inference_services.py +5 -0
  60. edsl/exceptions/jobs.py +33 -33
  61. edsl/exceptions/language_models.py +63 -63
  62. edsl/exceptions/prompts.py +15 -15
  63. edsl/exceptions/questions.py +109 -91
  64. edsl/exceptions/results.py +29 -29
  65. edsl/exceptions/scenarios.py +29 -22
  66. edsl/exceptions/surveys.py +37 -37
  67. edsl/inference_services/AnthropicService.py +106 -87
  68. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  69. edsl/inference_services/AvailableModelFetcher.py +215 -0
  70. edsl/inference_services/AwsBedrock.py +118 -120
  71. edsl/inference_services/AzureAI.py +215 -217
  72. edsl/inference_services/DeepInfraService.py +18 -18
  73. edsl/inference_services/GoogleService.py +143 -148
  74. edsl/inference_services/GroqService.py +20 -20
  75. edsl/inference_services/InferenceServiceABC.py +80 -147
  76. edsl/inference_services/InferenceServicesCollection.py +138 -97
  77. edsl/inference_services/MistralAIService.py +120 -123
  78. edsl/inference_services/OllamaService.py +18 -18
  79. edsl/inference_services/OpenAIService.py +236 -224
  80. edsl/inference_services/PerplexityService.py +160 -163
  81. edsl/inference_services/ServiceAvailability.py +135 -0
  82. edsl/inference_services/TestService.py +90 -89
  83. edsl/inference_services/TogetherAIService.py +172 -170
  84. edsl/inference_services/data_structures.py +134 -0
  85. edsl/inference_services/models_available_cache.py +118 -118
  86. edsl/inference_services/rate_limits_cache.py +25 -25
  87. edsl/inference_services/registry.py +41 -41
  88. edsl/inference_services/write_available.py +10 -10
  89. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  90. edsl/jobs/Answers.py +43 -56
  91. edsl/jobs/FetchInvigilator.py +47 -0
  92. edsl/jobs/InterviewTaskManager.py +98 -0
  93. edsl/jobs/InterviewsConstructor.py +50 -0
  94. edsl/jobs/Jobs.py +823 -898
  95. edsl/jobs/JobsChecks.py +172 -147
  96. edsl/jobs/JobsComponentConstructor.py +189 -0
  97. edsl/jobs/JobsPrompts.py +270 -268
  98. edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
  99. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  100. edsl/jobs/RequestTokenEstimator.py +30 -0
  101. edsl/jobs/__init__.py +1 -1
  102. edsl/jobs/async_interview_runner.py +138 -0
  103. edsl/jobs/buckets/BucketCollection.py +104 -63
  104. edsl/jobs/buckets/ModelBuckets.py +65 -65
  105. edsl/jobs/buckets/TokenBucket.py +283 -251
  106. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  107. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  108. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  109. edsl/jobs/data_structures.py +120 -0
  110. edsl/jobs/decorators.py +35 -0
  111. edsl/jobs/interviews/Interview.py +396 -661
  112. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  113. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  114. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  115. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  116. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  117. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  118. edsl/jobs/interviews/ReportErrors.py +66 -66
  119. edsl/jobs/interviews/interview_status_enum.py +9 -9
  120. edsl/jobs/jobs_status_enums.py +9 -0
  121. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  122. edsl/jobs/results_exceptions_handler.py +98 -0
  123. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
  124. edsl/jobs/runners/JobsRunnerStatus.py +297 -330
  125. edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
  126. edsl/jobs/tasks/TaskCreators.py +64 -64
  127. edsl/jobs/tasks/TaskHistory.py +470 -450
  128. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  129. edsl/jobs/tasks/task_status_enum.py +161 -163
  130. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  131. edsl/jobs/tokens/TokenUsage.py +34 -34
  132. edsl/language_models/ComputeCost.py +63 -0
  133. edsl/language_models/LanguageModel.py +626 -668
  134. edsl/language_models/ModelList.py +164 -155
  135. edsl/language_models/PriceManager.py +127 -0
  136. edsl/language_models/RawResponseHandler.py +106 -0
  137. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  138. edsl/language_models/ServiceDataSources.py +0 -0
  139. edsl/language_models/__init__.py +2 -3
  140. edsl/language_models/fake_openai_call.py +15 -15
  141. edsl/language_models/fake_openai_service.py +61 -61
  142. edsl/language_models/key_management/KeyLookup.py +63 -0
  143. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  144. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  145. edsl/language_models/key_management/__init__.py +0 -0
  146. edsl/language_models/key_management/models.py +131 -0
  147. edsl/language_models/model.py +256 -0
  148. edsl/language_models/repair.py +156 -156
  149. edsl/language_models/utilities.py +65 -64
  150. edsl/notebooks/Notebook.py +263 -258
  151. edsl/notebooks/NotebookToLaTeX.py +142 -0
  152. edsl/notebooks/__init__.py +1 -1
  153. edsl/prompts/Prompt.py +352 -362
  154. edsl/prompts/__init__.py +2 -2
  155. edsl/questions/ExceptionExplainer.py +77 -0
  156. edsl/questions/HTMLQuestion.py +103 -0
  157. edsl/questions/QuestionBase.py +518 -664
  158. edsl/questions/QuestionBasePromptsMixin.py +221 -217
  159. edsl/questions/QuestionBudget.py +227 -227
  160. edsl/questions/QuestionCheckBox.py +359 -359
  161. edsl/questions/QuestionExtract.py +180 -182
  162. edsl/questions/QuestionFreeText.py +113 -114
  163. edsl/questions/QuestionFunctional.py +166 -166
  164. edsl/questions/QuestionList.py +223 -231
  165. edsl/questions/QuestionMatrix.py +265 -0
  166. edsl/questions/QuestionMultipleChoice.py +330 -286
  167. edsl/questions/QuestionNumerical.py +151 -153
  168. edsl/questions/QuestionRank.py +314 -324
  169. edsl/questions/Quick.py +41 -41
  170. edsl/questions/SimpleAskMixin.py +74 -73
  171. edsl/questions/__init__.py +27 -26
  172. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
  173. edsl/questions/compose_questions.py +98 -98
  174. edsl/questions/data_structures.py +20 -0
  175. edsl/questions/decorators.py +21 -21
  176. edsl/questions/derived/QuestionLikertFive.py +76 -76
  177. edsl/questions/derived/QuestionLinearScale.py +90 -87
  178. edsl/questions/derived/QuestionTopK.py +93 -93
  179. edsl/questions/derived/QuestionYesNo.py +82 -82
  180. edsl/questions/descriptors.py +427 -413
  181. edsl/questions/loop_processor.py +149 -0
  182. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  183. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  184. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  185. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  186. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  187. edsl/questions/prompt_templates/question_list.jinja +17 -17
  188. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  189. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  190. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
  191. edsl/questions/question_registry.py +177 -177
  192. edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
  193. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
  194. edsl/questions/response_validator_factory.py +34 -0
  195. edsl/questions/settings.py +12 -12
  196. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  197. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  198. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  199. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  200. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  201. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  202. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  203. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  204. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  205. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  206. edsl/questions/templates/list/question_presentation.jinja +5 -5
  207. edsl/questions/templates/matrix/__init__.py +1 -0
  208. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  209. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  210. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  211. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  212. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  213. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  214. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  215. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  216. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  217. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  218. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  219. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  220. edsl/results/CSSParameterizer.py +108 -108
  221. edsl/results/Dataset.py +587 -424
  222. edsl/results/DatasetExportMixin.py +594 -731
  223. edsl/results/DatasetTree.py +295 -275
  224. edsl/results/MarkdownToDocx.py +122 -0
  225. edsl/results/MarkdownToPDF.py +111 -0
  226. edsl/results/Result.py +557 -465
  227. edsl/results/Results.py +1183 -1165
  228. edsl/results/ResultsExportMixin.py +45 -43
  229. edsl/results/ResultsGGMixin.py +121 -121
  230. edsl/results/TableDisplay.py +125 -198
  231. edsl/results/TextEditor.py +50 -0
  232. edsl/results/__init__.py +2 -2
  233. edsl/results/file_exports.py +252 -0
  234. edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
  235. edsl/results/{Selector.py → results_selector.py} +145 -135
  236. edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
  237. edsl/results/smart_objects.py +96 -0
  238. edsl/results/table_data_class.py +12 -0
  239. edsl/results/table_display.css +77 -77
  240. edsl/results/table_renderers.py +118 -0
  241. edsl/results/tree_explore.py +115 -115
  242. edsl/scenarios/ConstructDownloadLink.py +109 -0
  243. edsl/scenarios/DocumentChunker.py +102 -0
  244. edsl/scenarios/DocxScenario.py +16 -0
  245. edsl/scenarios/FileStore.py +511 -632
  246. edsl/scenarios/PdfExtractor.py +40 -0
  247. edsl/scenarios/Scenario.py +498 -601
  248. edsl/scenarios/ScenarioHtmlMixin.py +65 -64
  249. edsl/scenarios/ScenarioList.py +1458 -1287
  250. edsl/scenarios/ScenarioListExportMixin.py +45 -52
  251. edsl/scenarios/ScenarioListPdfMixin.py +239 -261
  252. edsl/scenarios/__init__.py +3 -4
  253. edsl/scenarios/directory_scanner.py +96 -0
  254. edsl/scenarios/file_methods.py +85 -0
  255. edsl/scenarios/handlers/__init__.py +13 -0
  256. edsl/scenarios/handlers/csv.py +38 -0
  257. edsl/scenarios/handlers/docx.py +76 -0
  258. edsl/scenarios/handlers/html.py +37 -0
  259. edsl/scenarios/handlers/json.py +111 -0
  260. edsl/scenarios/handlers/latex.py +5 -0
  261. edsl/scenarios/handlers/md.py +51 -0
  262. edsl/scenarios/handlers/pdf.py +68 -0
  263. edsl/scenarios/handlers/png.py +39 -0
  264. edsl/scenarios/handlers/pptx.py +105 -0
  265. edsl/scenarios/handlers/py.py +294 -0
  266. edsl/scenarios/handlers/sql.py +313 -0
  267. edsl/scenarios/handlers/sqlite.py +149 -0
  268. edsl/scenarios/handlers/txt.py +33 -0
  269. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
  270. edsl/scenarios/scenario_selector.py +156 -0
  271. edsl/shared.py +1 -1
  272. edsl/study/ObjectEntry.py +173 -173
  273. edsl/study/ProofOfWork.py +113 -113
  274. edsl/study/SnapShot.py +80 -80
  275. edsl/study/Study.py +521 -528
  276. edsl/study/__init__.py +4 -4
  277. edsl/surveys/ConstructDAG.py +92 -0
  278. edsl/surveys/DAG.py +148 -148
  279. edsl/surveys/EditSurvey.py +221 -0
  280. edsl/surveys/InstructionHandler.py +100 -0
  281. edsl/surveys/Memory.py +31 -31
  282. edsl/surveys/MemoryManagement.py +72 -0
  283. edsl/surveys/MemoryPlan.py +244 -244
  284. edsl/surveys/Rule.py +327 -326
  285. edsl/surveys/RuleCollection.py +385 -387
  286. edsl/surveys/RuleManager.py +172 -0
  287. edsl/surveys/Simulator.py +75 -0
  288. edsl/surveys/Survey.py +1280 -1801
  289. edsl/surveys/SurveyCSS.py +273 -261
  290. edsl/surveys/SurveyExportMixin.py +259 -259
  291. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
  292. edsl/surveys/SurveyQualtricsImport.py +284 -284
  293. edsl/surveys/SurveyToApp.py +141 -0
  294. edsl/surveys/__init__.py +5 -3
  295. edsl/surveys/base.py +53 -53
  296. edsl/surveys/descriptors.py +60 -56
  297. edsl/surveys/instructions/ChangeInstruction.py +48 -49
  298. edsl/surveys/instructions/Instruction.py +56 -65
  299. edsl/surveys/instructions/InstructionCollection.py +82 -77
  300. edsl/templates/error_reporting/base.html +23 -23
  301. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  302. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  303. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  304. edsl/templates/error_reporting/interview_details.html +115 -115
  305. edsl/templates/error_reporting/interviews.html +19 -19
  306. edsl/templates/error_reporting/overview.html +4 -4
  307. edsl/templates/error_reporting/performance_plot.html +1 -1
  308. edsl/templates/error_reporting/report.css +73 -73
  309. edsl/templates/error_reporting/report.html +117 -117
  310. edsl/templates/error_reporting/report.js +25 -25
  311. edsl/test_h +1 -0
  312. edsl/tools/__init__.py +1 -1
  313. edsl/tools/clusters.py +192 -192
  314. edsl/tools/embeddings.py +27 -27
  315. edsl/tools/embeddings_plotting.py +118 -118
  316. edsl/tools/plotting.py +112 -112
  317. edsl/tools/summarize.py +18 -18
  318. edsl/utilities/PrettyList.py +56 -0
  319. edsl/utilities/SystemInfo.py +28 -28
  320. edsl/utilities/__init__.py +22 -22
  321. edsl/utilities/ast_utilities.py +25 -25
  322. edsl/utilities/data/Registry.py +6 -6
  323. edsl/utilities/data/__init__.py +1 -1
  324. edsl/utilities/data/scooter_results.json +1 -1
  325. edsl/utilities/decorators.py +77 -77
  326. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  327. edsl/utilities/gcp_bucket/example.py +50 -0
  328. edsl/utilities/interface.py +627 -627
  329. edsl/utilities/is_notebook.py +18 -0
  330. edsl/utilities/is_valid_variable_name.py +11 -0
  331. edsl/utilities/naming_utilities.py +263 -263
  332. edsl/utilities/remove_edsl_version.py +24 -0
  333. edsl/utilities/repair_functions.py +28 -28
  334. edsl/utilities/restricted_python.py +70 -70
  335. edsl/utilities/utilities.py +436 -424
  336. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/LICENSE +21 -21
  337. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
  338. edsl-0.1.39.dev4.dist-info/RECORD +361 -0
  339. edsl/language_models/KeyLookup.py +0 -30
  340. edsl/language_models/registry.py +0 -190
  341. edsl/language_models/unused/ReplicateBase.py +0 -83
  342. edsl/results/ResultsDBMixin.py +0 -238
  343. edsl-0.1.39.dev3.dist-info/RECORD +0 -277
  344. {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/WHEEL +0 -0
edsl/agents/AgentList.py CHANGED
@@ -1,413 +1,551 @@
1
- """A list of Agent objects.
2
-
3
- Example usage:
4
-
5
- .. code-block:: python
6
-
7
- al = AgentList([Agent.example(), Agent.example()])
8
- len(al)
9
- 2
10
-
11
- """
12
-
13
- from __future__ import annotations
14
- import csv
15
- import json
16
- from collections import UserList
17
- from typing import Any, List, Optional, Union, TYPE_CHECKING
18
- from rich import print_json
19
- from rich.table import Table
20
- from simpleeval import EvalWithCompoundTypes
21
- from edsl.Base import Base
22
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
23
-
24
- from collections.abc import Iterable
25
-
26
- from edsl.exceptions.agents import AgentListError
27
-
28
- if TYPE_CHECKING:
29
- from edsl.scenarios.ScenarioList import ScenarioList
30
-
31
-
32
- def is_iterable(obj):
33
- return isinstance(obj, Iterable)
34
-
35
-
36
- class AgentList(UserList, Base):
37
- """A list of Agents."""
38
-
39
- __documentation__ = (
40
- "https://docs.expectedparrot.com/en/latest/agents.html#agentlist-class"
41
- )
42
-
43
- def __init__(self, data: Optional[list["Agent"]] = None):
44
- """Initialize a new AgentList.
45
-
46
- :param data: A list of Agents.
47
- """
48
- if data is not None:
49
- super().__init__(data)
50
- else:
51
- super().__init__()
52
-
53
- def shuffle(self, seed: Optional[str] = "edsl") -> AgentList:
54
- """Shuffle the AgentList.
55
-
56
- :param seed: The seed for the random number generator.
57
- """
58
- import random
59
-
60
- random.seed(seed)
61
- random.shuffle(self.data)
62
- return self
63
-
64
- def sample(self, n: int, seed: Optional[str] = None) -> AgentList:
65
- """Return a random sample of agents.
66
-
67
- :param n: The number of agents to sample.
68
- :param seed: The seed for the random number generator.
69
- """
70
- import random
71
-
72
- if seed:
73
- random.seed(seed)
74
- return AgentList(random.sample(self.data, n))
75
-
76
- def to_pandas(self):
77
- """Return a pandas DataFrame."""
78
- return self.to_scenario_list().to_pandas()
79
-
80
- def tally(self):
81
- return self.to_scenario_list().tally()
82
-
83
- def rename(self, old_name, new_name):
84
- """Rename a trait in the AgentList.
85
-
86
- :param old_name: The old name of the trait.
87
- :param new_name: The new name of the trait.
88
- """
89
- for agent in self.data:
90
- agent.rename(old_name, new_name)
91
- return self
92
-
93
- def select(self, *traits) -> AgentList:
94
- """Selects agents with only the references traits.
95
-
96
- >>> from edsl.agents.Agent import Agent
97
- >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
98
- >>> al.select('a')
99
- AgentList([Agent(traits = {'a': 1}), Agent(traits = {'a': 1})])
100
-
101
- """
102
-
103
- if len(traits) == 1:
104
- traits_to_select = [list(traits)[0]]
105
- else:
106
- traits_to_select = list(traits)
107
-
108
- return AgentList([agent.select(*traits_to_select) for agent in self.data])
109
-
110
- def filter(self, expression: str) -> AgentList:
111
- """
112
- Filter a list of agents based on an expression.
113
-
114
- >>> from edsl.agents.Agent import Agent
115
- >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
116
- >>> al.filter("b == 2")
117
- AgentList([Agent(traits = {'a': 1, 'b': 2})])
118
- """
119
-
120
- def create_evaluator(agent: "Agent"):
121
- """Create an evaluator for the given result.
122
- The 'combined_dict' is a mapping of all values for that Result object.
123
- """
124
- return EvalWithCompoundTypes(names=agent.traits)
125
-
126
- try:
127
- # iterates through all the results and evaluates the expression
128
- new_data = [
129
- agent for agent in self.data if create_evaluator(agent).eval(expression)
130
- ]
131
- except Exception as e:
132
- print(f"Exception:{e}")
133
- raise AgentListError(f"Error in filter. Exception:{e}")
134
-
135
- return AgentList(new_data)
136
-
137
- @property
138
- def all_traits(self):
139
- d = {}
140
- for agent in self:
141
- d.update(agent.traits)
142
- return list(d.keys())
143
-
144
- @classmethod
145
- def from_csv(cls, file_path: str, name_field: Optional[str] = None):
146
- """Load AgentList from a CSV file.
147
-
148
- >>> import csv
149
- >>> import os
150
- >>> with open('/tmp/agents.csv', 'w') as f:
151
- ... writer = csv.writer(f)
152
- ... _ = writer.writerow(['age', 'hair', 'height'])
153
- ... _ = writer.writerow([22, 'brown', 5.5])
154
- >>> al = AgentList.from_csv('/tmp/agents.csv')
155
- >>> al
156
- AgentList([Agent(traits = {'age': '22', 'hair': 'brown', 'height': '5.5'})])
157
- >>> al = AgentList.from_csv('/tmp/agents.csv', name_field='hair')
158
- >>> al
159
- AgentList([Agent(name = \"""brown\""", traits = {'age': '22', 'height': '5.5'})])
160
- >>> os.remove('/tmp/agents.csv')
161
-
162
- :param file_path: The path to the CSV file.
163
- :param name_field: The name of the field to use as the agent name.
164
- """
165
- from edsl.agents.Agent import Agent
166
-
167
- agent_list = []
168
- with open(file_path, "r") as f:
169
- reader = csv.DictReader(f)
170
- for row in reader:
171
- if "name" in row:
172
- import warnings
173
-
174
- warnings.warn("Using 'name' field in the CSV for the Agent name")
175
- name_field = "name"
176
- if name_field is not None:
177
- agent_name = row.pop(name_field)
178
- agent_list.append(Agent(traits=row, name=agent_name))
179
- else:
180
- agent_list.append(Agent(traits=row))
181
- return cls(agent_list)
182
-
183
- def translate_traits(self, values_codebook: dict[str, str]):
184
- """Translate traits to a new codebook.
185
-
186
- :param codebook: The new codebook.
187
- """
188
- for agent in self.data:
189
- agent.translate_traits(codebook)
190
- return self
191
-
192
- def remove_trait(self, trait: str):
193
- """Remove traits from the AgentList.
194
-
195
- :param traits: The traits to remove.
196
- >>> from edsl.agents.Agent import Agent
197
- >>> al = AgentList([Agent({'age': 22, 'hair': 'brown', 'height': 5.5}), Agent({'age': 22, 'hair': 'brown', 'height': 5.5})])
198
- >>> al.remove_trait('age')
199
- AgentList([Agent(traits = {'hair': 'brown', 'height': 5.5}), Agent(traits = {'hair': 'brown', 'height': 5.5})])
200
- """
201
- for agent in self.data:
202
- _ = agent.remove_trait(trait)
203
- return self
204
-
205
- def add_trait(self, trait, values):
206
- """Adds a new trait to every agent, with values taken from values.
207
-
208
- :param trait: The name of the trait.
209
- :param values: The valeues(s) of the trait. If a single value is passed, it is used for all agents.
210
-
211
- >>> al = AgentList.example()
212
- >>> al.add_trait('new_trait', 1)
213
- AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1})])
214
- >>> al.select('new_trait').to_scenario_list().to_list()
215
- [1, 1]
216
- >>> al.add_trait('new_trait', [1, 2, 3])
217
- Traceback (most recent call last):
218
- ...
219
- edsl.exceptions.agents.AgentListError: The passed values have to be the same length as the agent list.
220
- ...
221
- """
222
- if not is_iterable(values):
223
- value = values
224
- for agent in self.data:
225
- agent.add_trait(trait, value)
226
- return self
227
-
228
- if len(values) != len(self):
229
- raise AgentListError(
230
- "The passed values have to be the same length as the agent list."
231
- )
232
- for agent, value in zip(self.data, values):
233
- agent.add_trait(trait, value)
234
- return self
235
-
236
- @staticmethod
237
- def get_codebook(file_path: str):
238
- """Return the codebook for a CSV file.
239
-
240
- :param file_path: The path to the CSV file.
241
- """
242
- with open(file_path, "r") as f:
243
- reader = csv.DictReader(f)
244
- return {field: None for field in reader.fieldnames}
245
-
246
- def __hash__(self) -> int:
247
- from edsl.utilities.utilities import dict_hash
248
-
249
- return dict_hash(self.to_dict(add_edsl_version=False, sorted=True))
250
-
251
- def to_dict(self, sorted=False, add_edsl_version=True):
252
- """Serialize the AgentList to a dictionary."""
253
- if sorted:
254
- data = self.data[:]
255
- data.sort(key=lambda x: hash(x))
256
- else:
257
- data = self.data
258
-
259
- d = {
260
- "agent_list": [
261
- agent.to_dict(add_edsl_version=add_edsl_version) for agent in data
262
- ]
263
- }
264
- if add_edsl_version:
265
- from edsl import __version__
266
-
267
- d["edsl_version"] = __version__
268
- d["edsl_class_name"] = "AgentList"
269
-
270
- return d
271
-
272
- def __eq__(self, other: AgentList) -> bool:
273
- return self.to_dict(sorted=True, add_edsl_version=False) == other.to_dict(
274
- sorted=True, add_edsl_version=False
275
- )
276
-
277
- def __repr__(self):
278
- return f"AgentList({self.data})"
279
-
280
- def _summary(self):
281
- return {
282
- "EDSL Class": "AgentList",
283
- "Number of agents": len(self),
284
- "Agent trait fields": self.all_traits,
285
- }
286
-
287
- def _repr_html_(self):
288
- """Return an HTML representation of the AgentList."""
289
- footer = f"<a href={self.__documentation__}>(docs)</a>"
290
- return str(self.summary(format="html")) + footer
291
-
292
- def to_csv(self, file_path: str):
293
- """Save the AgentList to a CSV file.
294
-
295
- :param file_path: The path to the CSV file.
296
- """
297
- self.to_scenario_list().to_csv(file_path)
298
-
299
- def to_list(self, include_agent_name=False) -> list[tuple]:
300
- """Return a list of tuples."""
301
- return self.to_scenario_list(include_agent_name).to_list()
302
-
303
- def to_scenario_list(self, include_agent_name=False) -> ScenarioList:
304
- """Return a list of scenarios."""
305
- from edsl.scenarios.ScenarioList import ScenarioList
306
- from edsl.scenarios.Scenario import Scenario
307
-
308
- if include_agent_name:
309
- return ScenarioList(
310
- [
311
- Scenario(agent.traits | {"agent_name": agent.name})
312
- for agent in self.data
313
- ]
314
- )
315
- return ScenarioList([Scenario(agent.traits) for agent in self.data])
316
-
317
- def table(
318
- self,
319
- *fields,
320
- tablefmt: Optional[str] = None,
321
- pretty_labels: Optional[dict] = None,
322
- ) -> Table:
323
- return (
324
- self.to_scenario_list()
325
- .to_dataset()
326
- .table(*fields, tablefmt=tablefmt, pretty_labels=pretty_labels)
327
- )
328
-
329
- def tree(self, node_order: Optional[List[str]] = None):
330
- return self.to_scenario_list().tree(node_order)
331
-
332
- @classmethod
333
- @remove_edsl_version
334
- def from_dict(cls, data: dict) -> "AgentList":
335
- """Deserialize the dictionary back to an AgentList object.
336
-
337
- :param: data: A dictionary representing an AgentList.
338
- >>> from edsl.agents.Agent import Agent
339
- >>> al = AgentList([Agent.example(), Agent.example()])
340
- >>> al2 = AgentList.from_dict(al.to_dict())
341
- >>> al2 == al
342
- True
343
- """
344
- from edsl.agents.Agent import Agent
345
-
346
- agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
347
- return cls(agents)
348
-
349
- @classmethod
350
- def example(cls, randomize: bool = False) -> AgentList:
351
- """
352
- Returns an example AgentList instance.
353
-
354
- :param randomize: If True, uses Agent's randomize method.
355
- """
356
- from edsl.agents.Agent import Agent
357
-
358
- return cls([Agent.example(randomize), Agent.example(randomize)])
359
-
360
- @classmethod
361
- def from_list(self, trait_name: str, values: List[Any]):
362
- """Create an AgentList from a list of values.
363
-
364
- :param trait_name: The name of the trait.
365
- :param values: A list of values.
366
-
367
- >>> AgentList.from_list('age', [22, 23])
368
- AgentList([Agent(traits = {'age': 22}), Agent(traits = {'age': 23})])
369
- """
370
- from edsl.agents.Agent import Agent
371
-
372
- return AgentList([Agent({trait_name: value}) for value in values])
373
-
374
- def __mul__(self, other: AgentList) -> AgentList:
375
- """Takes the cross product of two AgentLists."""
376
- from itertools import product
377
-
378
- new_sl = []
379
- for s1, s2 in list(product(self, other)):
380
- new_sl.append(s1 + s2)
381
- return AgentList(new_sl)
382
-
383
- def code(self, string=True) -> Union[str, list[str]]:
384
- """Return code to construct an AgentList.
385
-
386
- >>> al = AgentList.example()
387
- >>> print(al.code())
388
- from edsl.agents.Agent import Agent
389
- from edsl.agents.AgentList import AgentList
390
- agent_list = AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
391
- """
392
- lines = [
393
- "from edsl.agents.Agent import Agent",
394
- "from edsl.agents.AgentList import AgentList",
395
- ]
396
- lines.append(f"agent_list = AgentList({self.data})")
397
- if string:
398
- return "\n".join(lines)
399
- return lines
400
-
401
- def rich_print(self) -> Table:
402
- """Display an object as a rich table."""
403
- table = Table(title="AgentList")
404
- table.add_column("Agents", style="bold")
405
- for agent in self.data:
406
- table.add_row(agent.rich_print())
407
- return table
408
-
409
-
410
- if __name__ == "__main__":
411
- import doctest
412
-
413
- doctest.testmod(optionflags=doctest.ELLIPSIS)
1
+ """A list of Agents
2
+ """
3
+
4
+ from __future__ import annotations
5
+ import csv
6
+ import sys
7
+ from collections import UserList
8
+ from collections.abc import Iterable
9
+
10
+ from typing import Any, List, Optional, Union, TYPE_CHECKING
11
+
12
+ from simpleeval import EvalWithCompoundTypes, NameNotDefined
13
+
14
+ from edsl.Base import Base
15
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
16
+ from edsl.exceptions.agents import AgentListError
17
+ from edsl.utilities.is_notebook import is_notebook
18
+ from edsl.results.ResultsExportMixin import ResultsExportMixin
19
+ import logging
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ if TYPE_CHECKING:
24
+ from edsl.scenarios.ScenarioList import ScenarioList
25
+ from edsl.agents.Agent import Agent
26
+ from pandas import DataFrame
27
+
28
+
29
+ def is_iterable(obj):
30
+ return isinstance(obj, Iterable)
31
+
32
+
33
+ class EmptyAgentList:
34
+ def __repr__(self):
35
+ return "Empty AgentList"
36
+
37
+
38
+ # ResultsExportMixin,
39
+ class AgentList(UserList, Base):
40
+ """A list of Agents."""
41
+
42
+ __documentation__ = (
43
+ "https://docs.expectedparrot.com/en/latest/agents.html#agentlist-class"
44
+ )
45
+
46
+ def __init__(self, data: Optional[list["Agent"]] = None):
47
+ """Initialize a new AgentList.
48
+
49
+ :param data: A list of Agents.
50
+ """
51
+ if data is not None:
52
+ super().__init__(data)
53
+ else:
54
+ super().__init__()
55
+
56
+ def shuffle(self, seed: Optional[str] = None) -> AgentList:
57
+ """Shuffle the AgentList.
58
+
59
+ :param seed: The seed for the random number generator.
60
+ """
61
+ import random
62
+
63
+ if seed is not None:
64
+ random.seed(seed)
65
+ random.shuffle(self.data)
66
+ return self
67
+
68
+ def sample(self, n: int, seed: Optional[str] = None) -> AgentList:
69
+ """Return a random sample of agents.
70
+
71
+ :param n: The number of agents to sample.
72
+ :param seed: The seed for the random number generator.
73
+ """
74
+ import random
75
+
76
+ if seed:
77
+ random.seed(seed)
78
+ return AgentList(random.sample(self.data, n))
79
+
80
+ def to_pandas(self) -> "DataFrame":
81
+ """Return a pandas DataFrame.
82
+
83
+ >>> from edsl.agents.Agent import Agent
84
+ >>> al = AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
85
+ >>> al.to_pandas()
86
+ age hair height
87
+ 0 22 brown 5.5
88
+ 1 22 brown 5.5
89
+ """
90
+ return self.to_scenario_list().to_pandas()
91
+
92
+ def tally(
93
+ self, *fields: Optional[str], top_n: Optional[int] = None, output="Dataset"
94
+ ) -> Union[dict, "Dataset"]:
95
+ """Tally the values of a field or perform a cross-tab of multiple fields.
96
+
97
+ :param fields: The field(s) to tally, multiple fields for cross-tabulation.
98
+
99
+ >>> al = AgentList.example()
100
+ >>> al.tally('age')
101
+ Dataset([{'age': [22]}, {'count': [2]}])
102
+ """
103
+ return self.to_scenario_list().tally(*fields, top_n=top_n, output=output)
104
+
105
+ def duplicate(self):
106
+ """Duplicate the AgentList.
107
+
108
+ >>> al = AgentList.example()
109
+ >>> al2 = al.duplicate()
110
+ >>> al2 == al
111
+ True
112
+ >>> id(al2) == id(al)
113
+ False
114
+ """
115
+ return AgentList([a.duplicate() for a in self.data])
116
+
117
+ def rename(self, old_name, new_name) -> AgentList:
118
+ """Rename a trait in the AgentList.
119
+
120
+ :param old_name: The old name of the trait.
121
+ :param new_name: The new name of the trait.
122
+ :param inplace: Whether to rename the trait in place.
123
+
124
+ >>> from edsl.agents.Agent import Agent
125
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
126
+ >>> al2 = al.rename('a', 'c')
127
+ >>> assert al2 == AgentList([Agent(traits = {'c': 1, 'b': 1}), Agent(traits = {'c': 1, 'b': 2})])
128
+ >>> assert al != al2
129
+ """
130
+ newagents = []
131
+ for agent in self:
132
+ newagents.append(agent.rename(old_name, new_name))
133
+ return AgentList(newagents)
134
+
135
+ def select(self, *traits) -> AgentList:
136
+ """Selects agents with only the references traits.
137
+
138
+ >>> from edsl.agents.Agent import Agent
139
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
140
+ >>> al.select('a')
141
+ AgentList([Agent(traits = {'a': 1}), Agent(traits = {'a': 1})])
142
+
143
+ """
144
+
145
+ if len(traits) == 1:
146
+ traits_to_select = [list(traits)[0]]
147
+ else:
148
+ traits_to_select = list(traits)
149
+
150
+ return AgentList([agent.select(*traits_to_select) for agent in self.data])
151
+
152
+ def filter(self, expression: str) -> AgentList:
153
+ """
154
+ Filter a list of agents based on an expression.
155
+
156
+ >>> from edsl.agents.Agent import Agent
157
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
158
+ >>> al.filter("b == 2")
159
+ AgentList([Agent(traits = {'a': 1, 'b': 2})])
160
+ """
161
+
162
+ def create_evaluator(agent: "Agent"):
163
+ """Create an evaluator for the given result.
164
+ The 'combined_dict' is a mapping of all values for that Result object.
165
+ """
166
+ return EvalWithCompoundTypes(names=agent.traits)
167
+
168
+ # iterates through all the results and evaluates the expression
169
+
170
+ try:
171
+ new_data = [
172
+ agent for agent in self.data if create_evaluator(agent).eval(expression)
173
+ ]
174
+ except NameNotDefined as e:
175
+ e = AgentListError(f"'{expression}' is not a valid expression.")
176
+ if is_notebook():
177
+ print(e, file=sys.stderr)
178
+ else:
179
+ raise e
180
+
181
+ return EmptyAgentList()
182
+
183
+ if len(new_data) == 0:
184
+ return EmptyAgentList()
185
+
186
+ return AgentList(new_data)
187
+
188
+ @property
189
+ def all_traits(self) -> list[str]:
190
+ """Return all traits in the AgentList.
191
+ >>> from edsl.agents.Agent import Agent
192
+ >>> agent_1 = Agent(traits = {'age': 22})
193
+ >>> agent_2 = Agent(traits = {'hair': 'brown'})
194
+ >>> al = AgentList([agent_1, agent_2])
195
+ >>> al.all_traits
196
+ ['age', 'hair']
197
+ """
198
+ d = {}
199
+ for agent in self:
200
+ d.update(agent.traits)
201
+ return list(d.keys())
202
+
203
+ @classmethod
204
+ def from_csv(cls, file_path: str, name_field: Optional[str] = None):
205
+ """Load AgentList from a CSV file.
206
+
207
+ >>> import csv
208
+ >>> import os
209
+ >>> with open('/tmp/agents.csv', 'w') as f:
210
+ ... writer = csv.writer(f)
211
+ ... _ = writer.writerow(['age', 'hair', 'height'])
212
+ ... _ = writer.writerow([22, 'brown', 5.5])
213
+ >>> al = AgentList.from_csv('/tmp/agents.csv')
214
+ >>> al
215
+ AgentList([Agent(traits = {'age': '22', 'hair': 'brown', 'height': '5.5'})])
216
+ >>> al = AgentList.from_csv('/tmp/agents.csv', name_field='hair')
217
+ >>> al
218
+ AgentList([Agent(name = \"""brown\""", traits = {'age': '22', 'height': '5.5'})])
219
+ >>> os.remove('/tmp/agents.csv')
220
+
221
+ :param file_path: The path to the CSV file.
222
+ :param name_field: The name of the field to use as the agent name.
223
+ """
224
+ from edsl.agents.Agent import Agent
225
+
226
+ agent_list = []
227
+ with open(file_path, "r") as f:
228
+ reader = csv.DictReader(f)
229
+ for row in reader:
230
+ if "name" in row:
231
+ import warnings
232
+
233
+ warnings.warn("Using 'name' field in the CSV for the Agent name")
234
+ name_field = "name"
235
+ if name_field is not None:
236
+ agent_name = row.pop(name_field)
237
+ agent_list.append(Agent(traits=row, name=agent_name))
238
+ else:
239
+ agent_list.append(Agent(traits=row))
240
+ return cls(agent_list)
241
+
242
+ def translate_traits(self, codebook: dict[str, str]):
243
+ """Translate traits to a new codebook.
244
+
245
+ :param codebook: The new codebook.
246
+
247
+ >>> al = AgentList.example()
248
+ >>> codebook = {'hair': {'brown':'Secret word for green'}}
249
+ >>> al.translate_traits(codebook)
250
+ AgentList([Agent(traits = {'age': 22, 'hair': 'Secret word for green', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'Secret word for green', 'height': 5.5})])
251
+ """
252
+ new_agents = []
253
+ for agent in self.data:
254
+ new_agents.append(agent.translate_traits(codebook))
255
+ return AgentList(new_agents)
256
+
257
+ def remove_trait(self, trait: str):
258
+ """Remove traits from the AgentList.
259
+
260
+ :param traits: The traits to remove.
261
+ >>> from edsl.agents.Agent import Agent
262
+ >>> al = AgentList([Agent({'age': 22, 'hair': 'brown', 'height': 5.5}), Agent({'age': 22, 'hair': 'brown', 'height': 5.5})])
263
+ >>> al.remove_trait('age')
264
+ AgentList([Agent(traits = {'hair': 'brown', 'height': 5.5}), Agent(traits = {'hair': 'brown', 'height': 5.5})])
265
+ """
266
+ agents = []
267
+ new_al = self.duplicate()
268
+ for agent in new_al.data:
269
+ agents.append(agent.remove_trait(trait))
270
+ return AgentList(agents)
271
+
272
+ def add_trait(self, trait: str, values: List[Any]) -> AgentList:
273
+ """Adds a new trait to every agent, with values taken from values.
274
+
275
+ :param trait: The name of the trait.
276
+ :param values: The valeues(s) of the trait. If a single value is passed, it is used for all agents.
277
+
278
+ >>> al = AgentList.example()
279
+ >>> new_al = al.add_trait('new_trait', 1)
280
+ >>> new_al.select('new_trait').to_scenario_list().to_list()
281
+ [1, 1]
282
+ >>> al.add_trait('new_trait', [1, 2, 3])
283
+ Traceback (most recent call last):
284
+ ...
285
+ edsl.exceptions.agents.AgentListError: The passed values have to be the same length as the agent list.
286
+ ...
287
+ """
288
+ if not is_iterable(values):
289
+ new_agents = []
290
+ value = values
291
+ for agent in self.data:
292
+ new_agents.append(agent.add_trait(trait, value))
293
+ return AgentList(new_agents)
294
+
295
+ if len(values) != len(self):
296
+ e = AgentListError(
297
+ "The passed values have to be the same length as the agent list."
298
+ )
299
+ if is_notebook():
300
+ print(e, file=sys.stderr)
301
+ else:
302
+ raise e
303
+ new_agents = []
304
+ for agent, value in zip(self.data, values):
305
+ new_agents.append(agent.add_trait(trait, value))
306
+ return AgentList(new_agents)
307
+
308
+ @staticmethod
309
+ def get_codebook(file_path: str):
310
+ """Return the codebook for a CSV file.
311
+
312
+ :param file_path: The path to the CSV file.
313
+ """
314
+ with open(file_path, "r") as f:
315
+ reader = csv.DictReader(f)
316
+ return {field: None for field in reader.fieldnames}
317
+
318
+ def __hash__(self) -> int:
319
+ """Return the hash of the AgentList.
320
+
321
+ >>> al = AgentList.example()
322
+ >>> hash(al)
323
+ 1681154913465662422
324
+ """
325
+ from edsl.utilities.utilities import dict_hash
326
+
327
+ return dict_hash(self.to_dict(add_edsl_version=False, sorted=True))
328
+
329
+ def to_dict(self, sorted=False, add_edsl_version=True):
330
+ """Serialize the AgentList to a dictionary.
331
+
332
+ >>> AgentList.example().to_dict(add_edsl_version=False)
333
+ {'agent_list': [{'traits': {'age': 22, 'hair': 'brown', 'height': 5.5}}, {'traits': {'age': 22, 'hair': 'brown', 'height': 5.5}}]}
334
+
335
+ """
336
+ if sorted:
337
+ data = self.data[:]
338
+ data.sort(key=lambda x: hash(x))
339
+ else:
340
+ data = self.data
341
+
342
+ d = {
343
+ "agent_list": [
344
+ agent.to_dict(add_edsl_version=add_edsl_version) for agent in data
345
+ ]
346
+ }
347
+ if add_edsl_version:
348
+ from edsl import __version__
349
+
350
+ d["edsl_version"] = __version__
351
+ d["edsl_class_name"] = "AgentList"
352
+
353
+ return d
354
+
355
+ def __eq__(self, other: AgentList) -> bool:
356
+ return self.to_dict(sorted=True, add_edsl_version=False) == other.to_dict(
357
+ sorted=True, add_edsl_version=False
358
+ )
359
+
360
+ def __repr__(self):
361
+ return f"AgentList({self.data})"
362
+
363
+ def _summary(self):
364
+ return {
365
+ "agents": len(self),
366
+ }
367
+
368
+ def set_codebook(self, codebook: dict[str, str]) -> AgentList:
369
+ """Set the codebook for the AgentList.
370
+
371
+ >>> from edsl.agents.Agent import Agent
372
+ >>> a = Agent(traits = {'hair': 'brown'})
373
+ >>> al = AgentList([a, a])
374
+ >>> _ = al.set_codebook({'hair': "Color of hair on driver's license"})
375
+ >>> al[0].codebook
376
+ {'hair': "Color of hair on driver's license"}
377
+
378
+
379
+ :param codebook: The codebook.
380
+ """
381
+ for agent in self.data:
382
+ agent.codebook = codebook
383
+
384
+ return self
385
+
386
+ def to_csv(self, file_path: str):
387
+ """Save the AgentList to a CSV file.
388
+
389
+ :param file_path: The path to the CSV file.
390
+ """
391
+ self.to_scenario_list().to_csv(file_path)
392
+
393
+ def to_list(self, include_agent_name=False) -> list[tuple]:
394
+ """Return a list of tuples."""
395
+ return self.to_scenario_list(include_agent_name).to_list()
396
+
397
+ def to_scenario_list(
398
+ self, include_agent_name: bool = False, include_instruction: bool = False
399
+ ) -> ScenarioList:
400
+ """Converts the agent to a scenario list."""
401
+ from edsl.scenarios.ScenarioList import ScenarioList
402
+ from edsl.scenarios.Scenario import Scenario
403
+
404
+ # raise NotImplementedError("This method is not implemented yet.")
405
+
406
+ scenario_list = ScenarioList()
407
+ for agent in self.data:
408
+ d = agent.traits
409
+ if include_agent_name:
410
+ d["agent_name"] = agent.name
411
+ if include_instruction:
412
+ d["instruction"] = agent.instruction
413
+ scenario_list.append(Scenario(d))
414
+ return scenario_list
415
+
416
+ # if include_agent_name:
417
+ # return ScenarioList(
418
+ # [
419
+ # Scenario(agent.traits | {"agent_name": agent.name} | })
420
+ # for agent in self.data
421
+ # ]
422
+ # )
423
+ # return ScenarioList([Scenario(agent.traits) for agent in self.data])
424
+
425
+ def table(
426
+ self,
427
+ *fields,
428
+ tablefmt: Optional[str] = None,
429
+ pretty_labels: Optional[dict] = None,
430
+ ) -> Table:
431
+ if len(self) == 0:
432
+ e = AgentListError("Cannot create a table from an empty AgentList.")
433
+ if is_notebook():
434
+ print(e, file=sys.stderr)
435
+ return None
436
+ else:
437
+ raise e
438
+ return (
439
+ self.to_scenario_list()
440
+ .to_dataset()
441
+ .table(*fields, tablefmt=tablefmt, pretty_labels=pretty_labels)
442
+ )
443
+
444
+ def to_dataset(self, traits_only: bool = True):
445
+ """
446
+ Convert the AgentList to a Dataset.
447
+
448
+ >>> from edsl.agents.AgentList import AgentList
449
+ >>> al = AgentList.example()
450
+ >>> al.to_dataset()
451
+ Dataset([{'age': [22, 22]}, {'hair': ['brown', 'brown']}, {'height': [5.5, 5.5]}])
452
+ >>> al.to_dataset(traits_only = False)
453
+ Dataset([{'age': [22, 22]}, {'hair': ['brown', 'brown']}, {'height': [5.5, 5.5]}, {'agent_parameters': [{'instruction': 'You are answering questions as if you were a human. Do not break character.', 'agent_name': None}, {'instruction': 'You are answering questions as if you were a human. Do not break character.', 'agent_name': None}]}])
454
+ """
455
+ from edsl.results.Dataset import Dataset
456
+ from collections import defaultdict
457
+
458
+ agent_trait_keys = []
459
+ for agent in self:
460
+ agent_keys = list(agent.traits.keys())
461
+ for key in agent_keys:
462
+ if key not in agent_trait_keys:
463
+ agent_trait_keys.append(key)
464
+
465
+ data = defaultdict(list)
466
+ for agent in self:
467
+ for trait_key in agent_trait_keys:
468
+ data[trait_key].append(agent.traits.get(trait_key, None))
469
+ if not traits_only:
470
+ data["agent_parameters"].append(
471
+ {"instruction": agent.instruction, "agent_name": agent.name}
472
+ )
473
+ return Dataset([{key: entry} for key, entry in data.items()])
474
+
475
+ def tree(self, node_order: Optional[List[str]] = None):
476
+ return self.to_scenario_list().tree(node_order)
477
+
478
+ @classmethod
479
+ @remove_edsl_version
480
+ def from_dict(cls, data: dict) -> "AgentList":
481
+ """Deserialize the dictionary back to an AgentList object.
482
+
483
+ :param: data: A dictionary representing an AgentList.
484
+ >>> from edsl.agents.Agent import Agent
485
+ >>> al = AgentList([Agent.example(), Agent.example()])
486
+ >>> al2 = AgentList.from_dict(al.to_dict())
487
+ >>> al2 == al
488
+ True
489
+ """
490
+ from edsl.agents.Agent import Agent
491
+
492
+ agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
493
+ return cls(agents)
494
+
495
+ @classmethod
496
+ def example(cls, randomize: bool = False) -> AgentList:
497
+ """
498
+ Returns an example AgentList instance.
499
+
500
+ :param randomize: If True, uses Agent's randomize method.
501
+ """
502
+ from edsl.agents.Agent import Agent
503
+
504
+ return cls([Agent.example(randomize), Agent.example(randomize)])
505
+
506
+ @classmethod
507
+ def from_list(self, trait_name: str, values: List[Any]):
508
+ """Create an AgentList from a list of values.
509
+
510
+ :param trait_name: The name of the trait.
511
+ :param values: A list of values.
512
+
513
+ >>> AgentList.from_list('age', [22, 23])
514
+ AgentList([Agent(traits = {'age': 22}), Agent(traits = {'age': 23})])
515
+ """
516
+ from edsl.agents.Agent import Agent
517
+
518
+ return AgentList([Agent({trait_name: value}) for value in values])
519
+
520
+ def __mul__(self, other: AgentList) -> AgentList:
521
+ """Takes the cross product of two AgentLists."""
522
+ from itertools import product
523
+
524
+ new_sl = []
525
+ for s1, s2 in list(product(self, other)):
526
+ new_sl.append(s1 + s2)
527
+ return AgentList(new_sl)
528
+
529
+ def code(self, string=True) -> Union[str, list[str]]:
530
+ """Return code to construct an AgentList.
531
+
532
+ >>> al = AgentList.example()
533
+ >>> print(al.code())
534
+ from edsl.agents.Agent import Agent
535
+ from edsl.agents.AgentList import AgentList
536
+ agent_list = AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
537
+ """
538
+ lines = [
539
+ "from edsl.agents.Agent import Agent",
540
+ "from edsl.agents.AgentList import AgentList",
541
+ ]
542
+ lines.append(f"agent_list = AgentList({self.data})")
543
+ if string:
544
+ return "\n".join(lines)
545
+ return lines
546
+
547
+
548
+ if __name__ == "__main__":
549
+ import doctest
550
+
551
+ doctest.testmod(optionflags=doctest.ELLIPSIS)