edsl 0.1.39.dev2__py3-none-any.whl → 0.1.39.dev3__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 (334) hide show
  1. edsl/Base.py +332 -385
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +49 -57
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +867 -1079
  7. edsl/agents/AgentList.py +413 -551
  8. edsl/agents/Invigilator.py +233 -285
  9. edsl/agents/InvigilatorBase.py +270 -254
  10. edsl/agents/PromptConstructor.py +354 -252
  11. edsl/agents/__init__.py +3 -2
  12. edsl/agents/descriptors.py +99 -99
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +279 -279
  26. edsl/config.py +157 -177
  27. edsl/conversation/Conversation.py +290 -290
  28. edsl/conversation/car_buying.py +58 -59
  29. edsl/conversation/chips.py +95 -95
  30. edsl/conversation/mug_negotiation.py +81 -81
  31. edsl/conversation/next_speaker_utilities.py +93 -93
  32. edsl/coop/PriceFetcher.py +54 -54
  33. edsl/coop/__init__.py +2 -2
  34. edsl/coop/coop.py +1028 -1090
  35. edsl/coop/utils.py +131 -131
  36. edsl/data/Cache.py +555 -562
  37. edsl/data/CacheEntry.py +233 -230
  38. edsl/data/CacheHandler.py +149 -170
  39. edsl/data/RemoteCacheSync.py +78 -78
  40. edsl/data/SQLiteDict.py +292 -292
  41. edsl/data/__init__.py +4 -5
  42. edsl/data/orm.py +10 -10
  43. edsl/data_transfer_models.py +73 -74
  44. edsl/enums.py +175 -195
  45. edsl/exceptions/BaseException.py +21 -21
  46. edsl/exceptions/__init__.py +54 -54
  47. edsl/exceptions/agents.py +42 -54
  48. edsl/exceptions/cache.py +5 -5
  49. edsl/exceptions/configuration.py +16 -16
  50. edsl/exceptions/coop.py +10 -10
  51. edsl/exceptions/data.py +14 -14
  52. edsl/exceptions/general.py +34 -34
  53. edsl/exceptions/jobs.py +33 -33
  54. edsl/exceptions/language_models.py +63 -63
  55. edsl/exceptions/prompts.py +15 -15
  56. edsl/exceptions/questions.py +91 -109
  57. edsl/exceptions/results.py +29 -29
  58. edsl/exceptions/scenarios.py +22 -29
  59. edsl/exceptions/surveys.py +37 -37
  60. edsl/inference_services/AnthropicService.py +87 -84
  61. edsl/inference_services/AwsBedrock.py +120 -118
  62. edsl/inference_services/AzureAI.py +217 -215
  63. edsl/inference_services/DeepInfraService.py +18 -18
  64. edsl/inference_services/GoogleService.py +148 -139
  65. edsl/inference_services/GroqService.py +20 -20
  66. edsl/inference_services/InferenceServiceABC.py +147 -80
  67. edsl/inference_services/InferenceServicesCollection.py +97 -122
  68. edsl/inference_services/MistralAIService.py +123 -120
  69. edsl/inference_services/OllamaService.py +18 -18
  70. edsl/inference_services/OpenAIService.py +224 -221
  71. edsl/inference_services/PerplexityService.py +163 -160
  72. edsl/inference_services/TestService.py +89 -92
  73. edsl/inference_services/TogetherAIService.py +170 -170
  74. edsl/inference_services/models_available_cache.py +118 -118
  75. edsl/inference_services/rate_limits_cache.py +25 -25
  76. edsl/inference_services/registry.py +41 -41
  77. edsl/inference_services/write_available.py +10 -10
  78. edsl/jobs/Answers.py +56 -43
  79. edsl/jobs/Jobs.py +898 -757
  80. edsl/jobs/JobsChecks.py +147 -172
  81. edsl/jobs/JobsPrompts.py +268 -270
  82. edsl/jobs/JobsRemoteInferenceHandler.py +239 -287
  83. edsl/jobs/__init__.py +1 -1
  84. edsl/jobs/buckets/BucketCollection.py +63 -104
  85. edsl/jobs/buckets/ModelBuckets.py +65 -65
  86. edsl/jobs/buckets/TokenBucket.py +251 -283
  87. edsl/jobs/interviews/Interview.py +661 -358
  88. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  89. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  90. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  91. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  92. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  93. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  94. edsl/jobs/interviews/ReportErrors.py +66 -66
  95. edsl/jobs/interviews/interview_status_enum.py +9 -9
  96. edsl/jobs/runners/JobsRunnerAsyncio.py +466 -421
  97. edsl/jobs/runners/JobsRunnerStatus.py +330 -330
  98. edsl/jobs/tasks/QuestionTaskCreator.py +242 -244
  99. edsl/jobs/tasks/TaskCreators.py +64 -64
  100. edsl/jobs/tasks/TaskHistory.py +450 -449
  101. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  102. edsl/jobs/tasks/task_status_enum.py +163 -161
  103. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  104. edsl/jobs/tokens/TokenUsage.py +34 -34
  105. edsl/language_models/KeyLookup.py +30 -0
  106. edsl/language_models/LanguageModel.py +668 -571
  107. edsl/language_models/ModelList.py +155 -153
  108. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  109. edsl/language_models/__init__.py +3 -2
  110. edsl/language_models/fake_openai_call.py +15 -15
  111. edsl/language_models/fake_openai_service.py +61 -61
  112. edsl/language_models/registry.py +190 -180
  113. edsl/language_models/repair.py +156 -156
  114. edsl/language_models/unused/ReplicateBase.py +83 -0
  115. edsl/language_models/utilities.py +64 -65
  116. edsl/notebooks/Notebook.py +258 -263
  117. edsl/notebooks/__init__.py +1 -1
  118. edsl/prompts/Prompt.py +362 -352
  119. edsl/prompts/__init__.py +2 -2
  120. edsl/questions/AnswerValidatorMixin.py +289 -334
  121. edsl/questions/QuestionBase.py +664 -509
  122. edsl/questions/QuestionBaseGenMixin.py +161 -165
  123. edsl/questions/QuestionBasePromptsMixin.py +217 -221
  124. edsl/questions/QuestionBudget.py +227 -227
  125. edsl/questions/QuestionCheckBox.py +359 -359
  126. edsl/questions/QuestionExtract.py +182 -182
  127. edsl/questions/QuestionFreeText.py +114 -113
  128. edsl/questions/QuestionFunctional.py +166 -166
  129. edsl/questions/QuestionList.py +231 -229
  130. edsl/questions/QuestionMultipleChoice.py +286 -330
  131. edsl/questions/QuestionNumerical.py +153 -151
  132. edsl/questions/QuestionRank.py +324 -314
  133. edsl/questions/Quick.py +41 -41
  134. edsl/questions/RegisterQuestionsMeta.py +71 -71
  135. edsl/questions/ResponseValidatorABC.py +174 -200
  136. edsl/questions/SimpleAskMixin.py +73 -74
  137. edsl/questions/__init__.py +26 -27
  138. edsl/questions/compose_questions.py +98 -98
  139. edsl/questions/decorators.py +21 -21
  140. edsl/questions/derived/QuestionLikertFive.py +76 -76
  141. edsl/questions/derived/QuestionLinearScale.py +87 -90
  142. edsl/questions/derived/QuestionTopK.py +93 -93
  143. edsl/questions/derived/QuestionYesNo.py +82 -82
  144. edsl/questions/descriptors.py +413 -427
  145. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  146. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  147. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  148. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  149. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  150. edsl/questions/prompt_templates/question_list.jinja +17 -17
  151. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  152. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  153. edsl/questions/question_registry.py +177 -177
  154. edsl/questions/settings.py +12 -12
  155. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  156. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  157. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  158. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  159. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  160. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  161. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  162. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  163. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  164. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  165. edsl/questions/templates/list/question_presentation.jinja +5 -5
  166. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  167. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  168. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  169. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  170. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  171. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  172. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  173. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  174. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  176. edsl/results/CSSParameterizer.py +108 -108
  177. edsl/results/Dataset.py +424 -587
  178. edsl/results/DatasetExportMixin.py +731 -653
  179. edsl/results/DatasetTree.py +275 -295
  180. edsl/results/Result.py +465 -451
  181. edsl/results/Results.py +1165 -1172
  182. edsl/results/ResultsDBMixin.py +238 -0
  183. edsl/results/ResultsExportMixin.py +43 -45
  184. edsl/results/ResultsFetchMixin.py +33 -33
  185. edsl/results/ResultsGGMixin.py +121 -121
  186. edsl/results/ResultsToolsMixin.py +98 -98
  187. edsl/results/Selector.py +135 -145
  188. edsl/results/TableDisplay.py +198 -125
  189. edsl/results/__init__.py +2 -2
  190. edsl/results/table_display.css +77 -77
  191. edsl/results/tree_explore.py +115 -115
  192. edsl/scenarios/FileStore.py +632 -511
  193. edsl/scenarios/Scenario.py +601 -498
  194. edsl/scenarios/ScenarioHtmlMixin.py +64 -65
  195. edsl/scenarios/ScenarioJoin.py +127 -131
  196. edsl/scenarios/ScenarioList.py +1287 -1430
  197. edsl/scenarios/ScenarioListExportMixin.py +52 -45
  198. edsl/scenarios/ScenarioListPdfMixin.py +261 -239
  199. edsl/scenarios/__init__.py +4 -3
  200. edsl/shared.py +1 -1
  201. edsl/study/ObjectEntry.py +173 -173
  202. edsl/study/ProofOfWork.py +113 -113
  203. edsl/study/SnapShot.py +80 -80
  204. edsl/study/Study.py +528 -521
  205. edsl/study/__init__.py +4 -4
  206. edsl/surveys/DAG.py +148 -148
  207. edsl/surveys/Memory.py +31 -31
  208. edsl/surveys/MemoryPlan.py +244 -244
  209. edsl/surveys/Rule.py +326 -327
  210. edsl/surveys/RuleCollection.py +387 -385
  211. edsl/surveys/Survey.py +1801 -1229
  212. edsl/surveys/SurveyCSS.py +261 -273
  213. edsl/surveys/SurveyExportMixin.py +259 -259
  214. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +179 -181
  215. edsl/surveys/SurveyQualtricsImport.py +284 -284
  216. edsl/surveys/__init__.py +3 -5
  217. edsl/surveys/base.py +53 -53
  218. edsl/surveys/descriptors.py +56 -60
  219. edsl/surveys/instructions/ChangeInstruction.py +49 -48
  220. edsl/surveys/instructions/Instruction.py +65 -56
  221. edsl/surveys/instructions/InstructionCollection.py +77 -82
  222. edsl/templates/error_reporting/base.html +23 -23
  223. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  224. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  225. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  226. edsl/templates/error_reporting/interview_details.html +115 -115
  227. edsl/templates/error_reporting/interviews.html +19 -19
  228. edsl/templates/error_reporting/overview.html +4 -4
  229. edsl/templates/error_reporting/performance_plot.html +1 -1
  230. edsl/templates/error_reporting/report.css +73 -73
  231. edsl/templates/error_reporting/report.html +117 -117
  232. edsl/templates/error_reporting/report.js +25 -25
  233. edsl/tools/__init__.py +1 -1
  234. edsl/tools/clusters.py +192 -192
  235. edsl/tools/embeddings.py +27 -27
  236. edsl/tools/embeddings_plotting.py +118 -118
  237. edsl/tools/plotting.py +112 -112
  238. edsl/tools/summarize.py +18 -18
  239. edsl/utilities/SystemInfo.py +28 -28
  240. edsl/utilities/__init__.py +22 -22
  241. edsl/utilities/ast_utilities.py +25 -25
  242. edsl/utilities/data/Registry.py +6 -6
  243. edsl/utilities/data/__init__.py +1 -1
  244. edsl/utilities/data/scooter_results.json +1 -1
  245. edsl/utilities/decorators.py +77 -77
  246. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  247. edsl/utilities/interface.py +627 -627
  248. edsl/utilities/naming_utilities.py +263 -263
  249. edsl/utilities/repair_functions.py +28 -28
  250. edsl/utilities/restricted_python.py +70 -70
  251. edsl/utilities/utilities.py +424 -436
  252. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/LICENSE +21 -21
  253. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/METADATA +10 -12
  254. edsl-0.1.39.dev3.dist-info/RECORD +277 -0
  255. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  256. edsl/agents/QuestionOptionProcessor.py +0 -172
  257. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  258. edsl/coop/CoopFunctionsMixin.py +0 -15
  259. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  260. edsl/exceptions/inference_services.py +0 -5
  261. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  262. edsl/inference_services/AvailableModelFetcher.py +0 -209
  263. edsl/inference_services/ServiceAvailability.py +0 -135
  264. edsl/inference_services/data_structures.py +0 -62
  265. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -188
  266. edsl/jobs/FetchInvigilator.py +0 -40
  267. edsl/jobs/InterviewTaskManager.py +0 -98
  268. edsl/jobs/InterviewsConstructor.py +0 -48
  269. edsl/jobs/JobsComponentConstructor.py +0 -189
  270. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  271. edsl/jobs/RequestTokenEstimator.py +0 -30
  272. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  273. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  274. edsl/jobs/decorators.py +0 -35
  275. edsl/jobs/jobs_status_enums.py +0 -9
  276. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  277. edsl/language_models/ComputeCost.py +0 -63
  278. edsl/language_models/PriceManager.py +0 -127
  279. edsl/language_models/RawResponseHandler.py +0 -106
  280. edsl/language_models/ServiceDataSources.py +0 -0
  281. edsl/language_models/key_management/KeyLookup.py +0 -63
  282. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  283. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  284. edsl/language_models/key_management/__init__.py +0 -0
  285. edsl/language_models/key_management/models.py +0 -131
  286. edsl/notebooks/NotebookToLaTeX.py +0 -142
  287. edsl/questions/ExceptionExplainer.py +0 -77
  288. edsl/questions/HTMLQuestion.py +0 -103
  289. edsl/questions/LoopProcessor.py +0 -149
  290. edsl/questions/QuestionMatrix.py +0 -265
  291. edsl/questions/ResponseValidatorFactory.py +0 -28
  292. edsl/questions/templates/matrix/__init__.py +0 -1
  293. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  294. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  295. edsl/results/MarkdownToDocx.py +0 -122
  296. edsl/results/MarkdownToPDF.py +0 -111
  297. edsl/results/TextEditor.py +0 -50
  298. edsl/results/smart_objects.py +0 -96
  299. edsl/results/table_data_class.py +0 -12
  300. edsl/results/table_renderers.py +0 -118
  301. edsl/scenarios/ConstructDownloadLink.py +0 -109
  302. edsl/scenarios/DirectoryScanner.py +0 -96
  303. edsl/scenarios/DocumentChunker.py +0 -102
  304. edsl/scenarios/DocxScenario.py +0 -16
  305. edsl/scenarios/PdfExtractor.py +0 -40
  306. edsl/scenarios/ScenarioSelector.py +0 -156
  307. edsl/scenarios/file_methods.py +0 -85
  308. edsl/scenarios/handlers/__init__.py +0 -13
  309. edsl/scenarios/handlers/csv.py +0 -38
  310. edsl/scenarios/handlers/docx.py +0 -76
  311. edsl/scenarios/handlers/html.py +0 -37
  312. edsl/scenarios/handlers/json.py +0 -111
  313. edsl/scenarios/handlers/latex.py +0 -5
  314. edsl/scenarios/handlers/md.py +0 -51
  315. edsl/scenarios/handlers/pdf.py +0 -68
  316. edsl/scenarios/handlers/png.py +0 -39
  317. edsl/scenarios/handlers/pptx.py +0 -105
  318. edsl/scenarios/handlers/py.py +0 -294
  319. edsl/scenarios/handlers/sql.py +0 -313
  320. edsl/scenarios/handlers/sqlite.py +0 -149
  321. edsl/scenarios/handlers/txt.py +0 -33
  322. edsl/surveys/ConstructDAG.py +0 -92
  323. edsl/surveys/EditSurvey.py +0 -221
  324. edsl/surveys/InstructionHandler.py +0 -100
  325. edsl/surveys/MemoryManagement.py +0 -72
  326. edsl/surveys/RuleManager.py +0 -172
  327. edsl/surveys/Simulator.py +0 -75
  328. edsl/surveys/SurveyToApp.py +0 -141
  329. edsl/utilities/PrettyList.py +0 -56
  330. edsl/utilities/is_notebook.py +0 -18
  331. edsl/utilities/is_valid_variable_name.py +0 -11
  332. edsl/utilities/remove_edsl_version.py +0 -24
  333. edsl-0.1.39.dev2.dist-info/RECORD +0 -352
  334. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/WHEEL +0 -0
edsl/agents/AgentList.py CHANGED
@@ -1,551 +1,413 @@
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)
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)