edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 (212) hide show
  1. edsl/Base.py +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
edsl/agents/Agent.py CHANGED
@@ -4,20 +4,46 @@ from __future__ import annotations
4
4
  import copy
5
5
  import inspect
6
6
  import types
7
- from typing import Callable, Optional, Union, Any, TYPE_CHECKING
7
+ from typing import (
8
+ Callable,
9
+ Optional,
10
+ Union,
11
+ Any,
12
+ TYPE_CHECKING,
13
+ Protocol,
14
+ runtime_checkable,
15
+ TypeVar,
16
+ )
17
+ from contextlib import contextmanager
18
+ from dataclasses import dataclass
19
+
20
+ # Type variable for the Agent class
21
+ A = TypeVar("A", bound="Agent")
8
22
 
9
23
  if TYPE_CHECKING:
10
- from edsl import Cache, Survey, Scenario
24
+ from edsl.data.Cache import Cache
25
+ from edsl.surveys.Survey import Survey
26
+ from edsl.scenarios.Scenario import Scenario
11
27
  from edsl.language_models import LanguageModel
12
28
  from edsl.surveys.MemoryPlan import MemoryPlan
13
29
  from edsl.questions import QuestionBase
14
30
  from edsl.agents.Invigilator import InvigilatorBase
31
+ from edsl.prompts import Prompt
32
+ from edsl.questions.QuestionBase import QuestionBase
33
+ from edsl.scenarios.Scenario import Scenario
34
+
35
+
36
+ @runtime_checkable
37
+ class DirectAnswerMethod(Protocol):
38
+ """Protocol defining the required signature for direct answer methods."""
39
+
40
+ def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
41
+
15
42
 
16
43
  from uuid import uuid4
17
44
 
18
45
  from edsl.Base import Base
19
- from edsl.prompts import Prompt
20
- from edsl.exceptions import QuestionScenarioRenderError
46
+ from edsl.exceptions.questions import QuestionScenarioRenderError
21
47
 
22
48
  from edsl.exceptions.agents import (
23
49
  AgentErrors,
@@ -34,17 +60,25 @@ from edsl.agents.descriptors import (
34
60
  )
35
61
  from edsl.utilities.decorators import (
36
62
  sync_wrapper,
37
- add_edsl_version,
38
- remove_edsl_version,
39
63
  )
64
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
40
65
  from edsl.data_transfer_models import AgentResponseDict
41
66
  from edsl.utilities.restricted_python import create_restricted_function
42
67
 
68
+ from edsl.scenarios.Scenario import Scenario
69
+
70
+
71
+ class AgentTraits(Scenario):
72
+ """A class representing the traits of an agent."""
73
+
74
+ def __repr__(self):
75
+ return f"{self.data}"
76
+
43
77
 
44
78
  class Agent(Base):
45
79
  """An class representing an agent that can answer questions."""
46
80
 
47
- __doc__ = "https://docs.expectedparrot.com/en/latest/agents.html"
81
+ __documentation__ = "https://docs.expectedparrot.com/en/latest/agents.html"
48
82
 
49
83
  default_instruction = """You are answering questions as if you were a human. Do not break character."""
50
84
 
@@ -77,6 +111,8 @@ class Agent(Base):
77
111
  :param instruction: Instructions for the agent in how to answer questions.
78
112
  :param trait_presentation_template: A template for how to present the agent's traits.
79
113
  :param dynamic_traits_function: A function that returns a dictionary of traits.
114
+ :param dynamic_traits_function_source_code: The source code for the dynamic traits function.
115
+ :param dynamic_traits_function_name: The name of the dynamic traits function.
80
116
 
81
117
  The `traits` parameter is a dictionary of traits that the agent has.
82
118
  These traits are used to construct a prompt that is presented to the LLM.
@@ -119,17 +155,59 @@ class Agent(Base):
119
155
  See see how these are used to actually construct the prompt that is presented to the LLM, see :py:class:`edsl.agents.Invigilator.InvigilatorBase`.
120
156
 
121
157
  """
158
+ self._initialize_basic_attributes(traits, name, codebook)
159
+ self._initialize_instruction(instruction)
160
+ self._initialize_dynamic_traits_function(
161
+ dynamic_traits_function,
162
+ dynamic_traits_function_source_code,
163
+ dynamic_traits_function_name,
164
+ )
165
+ self._initialize_answer_question_directly(
166
+ answer_question_directly_source_code, answer_question_directly_function_name
167
+ )
168
+ self._check_dynamic_traits_function()
169
+ self._initialize_traits_presentation_template(traits_presentation_template)
170
+ self.current_question = None
171
+
172
+ def _initialize_basic_attributes(self, traits, name, codebook) -> None:
173
+ """Initialize the basic attributes of the agent."""
122
174
  self.name = name
123
- self._traits = traits or dict()
175
+ self._traits = AgentTraits(traits or dict())
124
176
  self.codebook = codebook or dict()
177
+
178
+ def _initialize_instruction(self, instruction) -> None:
179
+ """Initialize the instruction for the agent i.e., how the agent should answer questions."""
125
180
  if instruction is None:
126
181
  self.instruction = self.default_instruction
182
+ self._instruction = self.default_instruction
183
+ self.set_instructions = False
127
184
  else:
128
185
  self.instruction = instruction
129
- # self.instruction = instruction or self.default_instruction
130
- self.dynamic_traits_function = dynamic_traits_function
186
+ self._instruction = instruction
187
+ self.set_instructions = True
188
+
189
+ def _initialize_traits_presentation_template(
190
+ self, traits_presentation_template
191
+ ) -> None:
192
+ """Initialize the traits presentation template. How the agent's traits are presented to the LLM."""
193
+ if traits_presentation_template is not None:
194
+ self._traits_presentation_template = traits_presentation_template
195
+ self.traits_presentation_template = traits_presentation_template
196
+ self.set_traits_presentation_template = True
197
+ else:
198
+ self.traits_presentation_template = "Your traits: {{traits}}"
199
+ self.set_traits_presentation_template = False
131
200
 
201
+ def _initialize_dynamic_traits_function(
202
+ self,
203
+ dynamic_traits_function,
204
+ dynamic_traits_function_source_code,
205
+ dynamic_traits_function_name,
206
+ ) -> None:
207
+ """Initialize the dynamic traits function i.e., a function that returns a dictionary of traits based on the question."""
132
208
  # Deal with dynamic traits function
209
+ self.dynamic_traits_function = dynamic_traits_function
210
+
133
211
  if self.dynamic_traits_function:
134
212
  self.dynamic_traits_function_name = self.dynamic_traits_function.__name__
135
213
  self.has_dynamic_traits_function = True
@@ -142,7 +220,11 @@ class Agent(Base):
142
220
  dynamic_traits_function_name, dynamic_traits_function
143
221
  )
144
222
 
145
- # Deal with direct answer function
223
+ def _initialize_answer_question_directly(
224
+ self,
225
+ answer_question_directly_source_code,
226
+ answer_question_directly_function_name,
227
+ ) -> None:
146
228
  if answer_question_directly_source_code:
147
229
  self.answer_question_directly_function_name = (
148
230
  answer_question_directly_function_name
@@ -154,18 +236,56 @@ class Agent(Base):
154
236
  bound_method = types.MethodType(protected_method, self)
155
237
  setattr(self, "answer_question_directly", bound_method)
156
238
 
157
- self._check_dynamic_traits_function()
158
-
159
- self.current_question = None
160
-
239
+ def _initialize_traits_presentation_template(
240
+ self, traits_presentation_template
241
+ ) -> None:
161
242
  if traits_presentation_template is not None:
162
243
  self._traits_presentation_template = traits_presentation_template
163
244
  self.traits_presentation_template = traits_presentation_template
245
+ self.set_traits_presentation_template = True
164
246
  else:
165
247
  self.traits_presentation_template = "Your traits: {{traits}}"
248
+ self.set_traits_presentation_template = False
249
+
250
+ def duplicate(self) -> Agent:
251
+ """Return a duplicate of the agent.
252
+
253
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
254
+ >>> a2 = a.duplicate()
255
+ >>> a2 == a
256
+ True
257
+ >>> id(a) == id(a2)
258
+ False
259
+ >>> def f(self, question, scenario): return "I am a direct answer."
260
+ >>> a.add_direct_question_answering_method(f)
261
+ >>> hasattr(a, "answer_question_directly")
262
+ True
263
+ >>> a2 = a.duplicate()
264
+ >>> a2.answer_question_directly(None, None)
265
+ 'I am a direct answer.'
266
+
267
+ >>> a = Agent(traits = {'age': 10}, instruction = "Have fun!")
268
+ >>> a2 = a.duplicate()
269
+ >>> a2.instruction
270
+ 'Have fun!'
271
+ """
272
+ new_agent = Agent.from_dict(self.to_dict())
273
+ if hasattr(self, "answer_question_directly"):
274
+ answer_question_directly = self.answer_question_directly
275
+ newf = lambda self, question, scenario: answer_question_directly(
276
+ question, scenario
277
+ )
278
+ new_agent.add_direct_question_answering_method(newf)
279
+ if hasattr(self, "dynamic_traits_function"):
280
+ dynamic_traits_function = self.dynamic_traits_function
281
+ new_agent.dynamic_traits_function = dynamic_traits_function
282
+ return new_agent
166
283
 
167
284
  @property
168
285
  def agent_persona(self) -> Prompt:
286
+ """Return the agent persona template."""
287
+ from edsl.prompts.Prompt import Prompt
288
+
169
289
  return Prompt(text=self.traits_presentation_template)
170
290
 
171
291
  def prompt(self) -> str:
@@ -241,59 +361,111 @@ class Agent(Base):
241
361
  else:
242
362
  return self.dynamic_traits_function()
243
363
  else:
244
- return self._traits
245
-
246
- def _repr_html_(self):
247
- # d = self.to_dict(add_edsl_version=False)
248
- d = self.traits
249
- data = [[k, v] for k, v in d.items()]
250
- from tabulate import tabulate
364
+ return dict(self._traits)
365
+
366
+ @contextmanager
367
+ def modify_traits_context(self):
368
+ self._check_before_modifying_traits()
369
+ try:
370
+ yield
371
+ finally:
372
+ self._traits = AgentTraits(self._traits)
373
+
374
+ def _check_before_modifying_traits(self):
375
+ """Check before modifying traits."""
376
+ if self.has_dynamic_traits_function:
377
+ raise AgentErrors(
378
+ "You cannot modify the traits of an agent that has a dynamic traits function.",
379
+ "If you want to modify the traits, you should remove the dynamic traits function.",
380
+ )
251
381
 
252
- table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
253
- return f"<pre>{table}</pre>"
382
+ @traits.setter
383
+ def traits(self, traits: dict[str, str]):
384
+ with self.modify_traits_context():
385
+ self._traits = traits
386
+ # self._check_before_modifying_traits()
387
+ # self._traits = AgentTraits(traits)
254
388
 
255
389
  def rename(
256
- self, old_name_or_dict: Union[str, dict], new_name: Optional[str] = None
390
+ self,
391
+ old_name_or_dict: Union[str, dict[str, str]],
392
+ new_name: Optional[str] = None,
257
393
  ) -> Agent:
258
394
  """Rename a trait.
259
395
 
396
+ :param old_name_or_dict: The old name of the trait or a dictionary of old names and new names.
397
+ :param new_name: The new name of the trait.
398
+
260
399
  Example usage:
261
400
 
262
401
  >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
263
- >>> a.rename("age", "years") == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
402
+ >>> newa = a.rename("age", "years")
403
+ >>> newa == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
264
404
  True
265
405
 
266
- >>> a.rename({'years': 'smage'})
267
- Agent(traits = {'hair': 'brown', 'height': 5.5, 'smage': 10})
406
+ >>> newa.rename({'years': 'smage'}) == Agent(traits = {'smage': 10, 'hair': 'brown', 'height': 5.5})
407
+ True
268
408
 
269
409
  """
270
- if isinstance(old_name_or_dict, dict) and new_name is None:
271
- for old_name, new_name in old_name_or_dict.items():
272
- self = self._rename(old_name, new_name)
273
- return self
274
-
410
+ self._check_before_modifying_traits()
275
411
  if isinstance(old_name_or_dict, dict) and new_name:
276
412
  raise AgentErrors(
277
413
  f"You passed a dict: {old_name_or_dict} and a new name: {new_name}. You should pass only a dict."
278
414
  )
279
415
 
416
+ if isinstance(old_name_or_dict, dict) and new_name is None:
417
+ return self._rename_dict(old_name_or_dict)
418
+
280
419
  if isinstance(old_name_or_dict, str):
281
- self._rename(old_name_or_dict, new_name)
282
- return self
420
+ return self._rename(old_name_or_dict, new_name)
283
421
 
284
422
  raise AgentErrors("Something is not right with Agent renaming")
285
423
 
424
+ def _rename_dict(self, renaming_dict: dict[str, str]) -> Agent:
425
+ """
426
+ Internal method to rename traits using a dictionary.
427
+ The keys should all be old names and the values should all be new names.
428
+
429
+ Example usage:
430
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
431
+ >>> a._rename_dict({"age": "years", "height": "feet"})
432
+ Agent(traits = {'years': 10, 'hair': 'brown', 'feet': 5.5})
433
+
434
+ """
435
+ try:
436
+ assert all(k in self.traits for k in renaming_dict.keys())
437
+ except AssertionError:
438
+ raise AgentErrors(
439
+ f"The trait(s) {set(renaming_dict.keys()) - set(self.traits.keys())} do not exist in the agent's traits, which are {self.traits}."
440
+ )
441
+ new_agent = self.duplicate()
442
+ new_agent.traits = {renaming_dict.get(k, k): v for k, v in self.traits.items()}
443
+ return new_agent
444
+
286
445
  def _rename(self, old_name: str, new_name: str) -> Agent:
287
446
  """Rename a trait.
288
447
 
289
448
  Example usage:
290
449
 
291
450
  >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
292
- >>> a.rename("age", "years") == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
293
- True
451
+ >>> a._rename(old_name="age", new_name="years")
452
+ Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
453
+
294
454
  """
295
- self.traits[new_name] = self.traits.pop(old_name)
296
- return self
455
+ try:
456
+ assert old_name in self.traits
457
+ except AssertionError:
458
+ raise AgentErrors(
459
+ f"The trait '{old_name}' does not exist in the agent's traits, which are {self.traits}."
460
+ )
461
+ newagent = self.duplicate()
462
+ newagent.traits = {
463
+ new_name if k == old_name else k: v for k, v in self.traits.items()
464
+ }
465
+ newagent.codebook = {
466
+ new_name if k == old_name else k: v for k, v in self.codebook.items()
467
+ }
468
+ return newagent
297
469
 
298
470
  def __getitem__(self, key):
299
471
  """Allow for accessing traits using the bracket notation.
@@ -324,7 +496,7 @@ class Agent(Base):
324
496
 
325
497
  def add_direct_question_answering_method(
326
498
  self,
327
- method: Callable,
499
+ method: DirectAnswerMethod,
328
500
  validate_response: bool = False,
329
501
  translate_response: bool = False,
330
502
  ) -> None:
@@ -353,6 +525,12 @@ class Agent(Base):
353
525
  self.validate_response = validate_response
354
526
  self.translate_response = translate_response
355
527
 
528
+ # if not isinstance(method, DirectAnswerMethod):
529
+ # raise AgentDirectAnswerFunctionError(
530
+ # f"Method {method} does not match required signature. "
531
+ # "Must take (self, question, scenario) parameters."
532
+ # )
533
+
356
534
  signature = inspect.signature(method)
357
535
  for argument in ["question", "scenario", "self"]:
358
536
  if argument not in signature.parameters:
@@ -371,12 +549,11 @@ class Agent(Base):
371
549
  survey: Optional["Survey"] = None,
372
550
  scenario: Optional["Scenario"] = None,
373
551
  model: Optional["LanguageModel"] = None,
374
- debug: bool = False,
375
552
  memory_plan: Optional["MemoryPlan"] = None,
376
553
  current_answers: Optional[dict] = None,
377
554
  iteration: int = 1,
378
- sidecar_model=None,
379
555
  raise_validation_errors: bool = True,
556
+ key_lookup: Optional["KeyLookup"] = None,
380
557
  ) -> "InvigilatorBase":
381
558
  """Create an Invigilator.
382
559
 
@@ -391,7 +568,9 @@ class Agent(Base):
391
568
  An invigator is an object that is responsible for administering a question to an agent and
392
569
  recording the responses.
393
570
  """
394
- from edsl import Model, Scenario
571
+ from edsl.language_models.model import Model
572
+
573
+ from edsl.scenarios.Scenario import Scenario
395
574
 
396
575
  cache = cache
397
576
  self.current_question = question
@@ -402,13 +581,12 @@ class Agent(Base):
402
581
  scenario=scenario,
403
582
  survey=survey,
404
583
  model=model,
405
- debug=debug,
406
584
  memory_plan=memory_plan,
407
585
  current_answers=current_answers,
408
586
  iteration=iteration,
409
587
  cache=cache,
410
- sidecar_model=sidecar_model,
411
588
  raise_validation_errors=raise_validation_errors,
589
+ key_lookup=key_lookup,
412
590
  )
413
591
  if hasattr(self, "validate_response"):
414
592
  invigilator.validate_response = self.validate_response
@@ -428,6 +606,7 @@ class Agent(Base):
428
606
  memory_plan: Optional[MemoryPlan] = None,
429
607
  current_answers: Optional[dict] = None,
430
608
  iteration: int = 0,
609
+ key_lookup: Optional["KeyLookup"] = None,
431
610
  ) -> AgentResponseDict:
432
611
  """
433
612
  Answer a posed question.
@@ -442,7 +621,7 @@ class Agent(Base):
442
621
 
443
622
  >>> a = Agent(traits = {})
444
623
  >>> a.add_direct_question_answering_method(lambda self, question, scenario: "I am a direct answer.")
445
- >>> from edsl import QuestionFreeText
624
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
446
625
  >>> q = QuestionFreeText.example()
447
626
  >>> a.answer_question(question = q, cache = False).answer
448
627
  'I am a direct answer.'
@@ -457,16 +636,35 @@ class Agent(Base):
457
636
  scenario=scenario,
458
637
  survey=survey,
459
638
  model=model,
460
- debug=debug,
461
639
  memory_plan=memory_plan,
462
640
  current_answers=current_answers,
463
641
  iteration=iteration,
642
+ key_lookup=key_lookup,
464
643
  )
465
644
  response: AgentResponseDict = await invigilator.async_answer_question()
466
645
  return response
467
646
 
468
647
  answer_question = sync_wrapper(async_answer_question)
469
648
 
649
+ def _get_invigilator_class(self, question: QuestionBase) -> Type[InvigilatorBase]:
650
+ """Get the invigilator class for a question.
651
+
652
+ This method returns the invigilator class that should be used to answer a question.
653
+ The invigilator class is determined by the type of question and the type of agent.
654
+ """
655
+ from edsl.agents.Invigilator import (
656
+ InvigilatorHuman,
657
+ InvigilatorFunctional,
658
+ InvigilatorAI,
659
+ )
660
+
661
+ if hasattr(question, "answer_question_directly"):
662
+ return InvigilatorFunctional
663
+ elif hasattr(self, "answer_question_directly"):
664
+ return InvigilatorHuman
665
+ else:
666
+ return InvigilatorAI
667
+
470
668
  def _create_invigilator(
471
669
  self,
472
670
  question: QuestionBase,
@@ -474,53 +672,25 @@ class Agent(Base):
474
672
  scenario: Optional[Scenario] = None,
475
673
  model: Optional[LanguageModel] = None,
476
674
  survey: Optional[Survey] = None,
477
- debug: bool = False,
478
675
  memory_plan: Optional[MemoryPlan] = None,
479
676
  current_answers: Optional[dict] = None,
480
677
  iteration: int = 0,
481
- sidecar_model=None,
482
678
  raise_validation_errors: bool = True,
679
+ key_lookup: Optional["KeyLookup"] = None,
483
680
  ) -> "InvigilatorBase":
484
681
  """Create an Invigilator."""
485
- from edsl import Model
486
- from edsl import Scenario
682
+ from edsl.language_models.model import Model
683
+ from edsl.scenarios.Scenario import Scenario
487
684
 
488
685
  model = model or Model()
489
686
  scenario = scenario or Scenario()
490
687
 
491
- from edsl.agents.Invigilator import (
492
- InvigilatorHuman,
493
- InvigilatorFunctional,
494
- InvigilatorAI,
495
- InvigilatorBase,
496
- )
497
-
498
688
  if cache is None:
499
689
  from edsl.data.Cache import Cache
500
690
 
501
691
  cache = Cache()
502
692
 
503
- if debug:
504
- raise NotImplementedError("Debug mode is not yet implemented.")
505
- # use the question's _simulate_answer method
506
- # invigilator_class = InvigilatorDebug
507
- elif hasattr(question, "answer_question_directly"):
508
- # It's a functional question and the answer only depends on the agent's traits & the scenario
509
- invigilator_class = InvigilatorFunctional
510
- elif hasattr(self, "answer_question_directly"):
511
- # this of the case where the agent has a method that can answer the question directly
512
- # this occurrs when 'answer_question_directly' has been given to the
513
- # which happens when the agent is created from an existing survey
514
- invigilator_class = InvigilatorHuman
515
- else:
516
- # this means an LLM agent will be used. This is the standard case.
517
- invigilator_class = InvigilatorAI
518
-
519
- if sidecar_model is not None:
520
- # this is the case when a 'simple' model is being used
521
- from edsl.agents.Invigilator import InvigilatorSidecar
522
-
523
- invigilator_class = InvigilatorSidecar
693
+ invigilator_class = self._get_invigilator_class(question)
524
694
 
525
695
  invigilator = invigilator_class(
526
696
  self,
@@ -532,22 +702,24 @@ class Agent(Base):
532
702
  current_answers=current_answers,
533
703
  iteration=iteration,
534
704
  cache=cache,
535
- sidecar_model=sidecar_model,
536
705
  raise_validation_errors=raise_validation_errors,
706
+ key_lookup=key_lookup,
537
707
  )
538
708
  return invigilator
539
709
 
540
710
  def select(self, *traits: str) -> Agent:
541
711
  """Selects agents with only the references traits
542
712
 
543
- >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
713
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
714
+ >>> a
715
+ Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5}, codebook = {'age': 'Their age is'})
544
716
 
545
717
 
546
718
  >>> a.select("age", "height")
547
- Agent(traits = {'age': 10, 'height': 5.5})
719
+ Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is'})
548
720
 
549
- >>> a.select("age")
550
- Agent(traits = {'age': 10})
721
+ >>> a.select("height")
722
+ Agent(traits = {'height': 5.5})
551
723
 
552
724
  """
553
725
 
@@ -556,7 +728,17 @@ class Agent(Base):
556
728
  else:
557
729
  traits_to_select = list(traits)
558
730
 
559
- return Agent(traits={trait: self.traits[trait] for trait in traits_to_select})
731
+ def _remove_none(d):
732
+ return {k: v for k, v in d.items() if v is not None}
733
+
734
+ newagent = self.duplicate()
735
+ newagent.traits = {
736
+ trait: self.traits.get(trait, None) for trait in traits_to_select
737
+ }
738
+ newagent.codebook = _remove_none(
739
+ {trait: self.codebook.get(trait, None) for trait in traits_to_select}
740
+ )
741
+ return newagent
560
742
 
561
743
  def __add__(self, other_agent: Optional[Agent] = None) -> Agent:
562
744
  """
@@ -575,6 +757,10 @@ class Agent(Base):
575
757
  ...
576
758
  edsl.exceptions.agents.AgentCombinationError: The agents have overlapping traits: {'age'}.
577
759
  ...
760
+ >>> a1 = Agent(traits = {"age": 10}, codebook = {"age": "Their age is"})
761
+ >>> a2 = Agent(traits = {"height": 5.5}, codebook = {"height": "Their height is"})
762
+ >>> a1 + a2
763
+ Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is', 'height': 'Their height is'})
578
764
  """
579
765
  if other_agent is None:
580
766
  return self
@@ -583,9 +769,14 @@ class Agent(Base):
583
769
  f"The agents have overlapping traits: {common_traits}."
584
770
  )
585
771
  else:
586
- new_agent = Agent(traits=copy.deepcopy(self.traits))
587
- new_agent.traits.update(other_agent.traits)
588
- return new_agent
772
+ new_codebook = copy.deepcopy(self.codebook) | copy.deepcopy(
773
+ other_agent.codebook
774
+ )
775
+ d = self.traits | other_agent.traits
776
+ newagent = self.duplicate()
777
+ newagent.traits = d
778
+ newagent.codebook = new_codebook
779
+ return newagent
589
780
 
590
781
  def __eq__(self, other: Agent) -> bool:
591
782
  """Check if two agents are equal.
@@ -602,7 +793,11 @@ class Agent(Base):
602
793
  return self.data == other.data
603
794
 
604
795
  def __getattr__(self, name):
605
- # This will be called only if 'name' is not found in the usual places
796
+ """
797
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
798
+ >>> a.age
799
+ 10
800
+ """
606
801
  if name == "has_dynamic_traits_function":
607
802
  return self.has_dynamic_traits_function
608
803
 
@@ -624,12 +819,6 @@ class Agent(Base):
624
819
  if "_traits" not in self.__dict__:
625
820
  self._traits = {}
626
821
 
627
- def print(self) -> None:
628
- from rich import print_json
629
- import json
630
-
631
- print_json(json.dumps(self.to_dict()))
632
-
633
822
  def __repr__(self) -> str:
634
823
  """Return representation of Agent."""
635
824
  class_name = self.__class__.__name__
@@ -640,14 +829,6 @@ class Agent(Base):
640
829
  ]
641
830
  return f"{class_name}({', '.join(items)})"
642
831
 
643
- # def _repr_html_(self):
644
- # from edsl.utilities.utilities import data_to_html
645
-
646
- # return data_to_html(self.to_dict())
647
-
648
- #######################
649
- # SERIALIZATION METHODS
650
- #######################
651
832
  @property
652
833
  def data(self) -> dict:
653
834
  """Format the data for serialization.
@@ -678,9 +859,9 @@ class Agent(Base):
678
859
  if dynamic_traits_func:
679
860
  func = inspect.getsource(dynamic_traits_func)
680
861
  raw_data["dynamic_traits_function_source_code"] = func
681
- raw_data[
682
- "dynamic_traits_function_name"
683
- ] = self.dynamic_traits_function_name
862
+ raw_data["dynamic_traits_function_name"] = (
863
+ self.dynamic_traits_function_name
864
+ )
684
865
  if hasattr(self, "answer_question_directly"):
685
866
  raw_data.pop(
686
867
  "answer_question_directly", None
@@ -694,18 +875,23 @@ class Agent(Base):
694
875
  raw_data["answer_question_directly_source_code"] = inspect.getsource(
695
876
  answer_question_directly_func
696
877
  )
697
- raw_data[
698
- "answer_question_directly_function_name"
699
- ] = self.answer_question_directly_function_name
878
+ raw_data["answer_question_directly_function_name"] = (
879
+ self.answer_question_directly_function_name
880
+ )
881
+ raw_data["traits"] = dict(raw_data["traits"])
700
882
 
701
883
  return raw_data
702
884
 
703
885
  def __hash__(self) -> int:
886
+ """Return a hash of the agent.
887
+
888
+ >>> hash(Agent.example())
889
+ 2067581884874391607
890
+ """
704
891
  from edsl.utilities.utilities import dict_hash
705
892
 
706
893
  return dict_hash(self.to_dict(add_edsl_version=False))
707
894
 
708
- # @add_edsl_version
709
895
  def to_dict(self, add_edsl_version=True) -> dict[str, Union[dict, bool]]:
710
896
  """Serialize to a dictionary with EDSL info.
711
897
 
@@ -713,9 +899,22 @@ class Agent(Base):
713
899
 
714
900
  >>> a = Agent(name = "Steve", traits = {"age": 10, "hair": "brown", "height": 5.5})
715
901
  >>> a.to_dict()
716
- {'name': 'Steve', 'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'edsl_version': '...', 'edsl_class_name': 'Agent'}
902
+ {'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'name': 'Steve', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
903
+
904
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, instruction = "Have fun.")
905
+ >>> a.to_dict()
906
+ {'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'instruction': 'Have fun.', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
717
907
  """
718
- d = copy.deepcopy(self.data)
908
+ d = {}
909
+ d["traits"] = copy.deepcopy(dict(self._traits))
910
+ if self.name:
911
+ d["name"] = self.name
912
+ if self.set_instructions:
913
+ d["instruction"] = self.instruction
914
+ if self.set_traits_presentation_template:
915
+ d["traits_presentation_template"] = self.traits_presentation_template
916
+ if self.codebook:
917
+ d["codebook"] = self.codebook
719
918
  if add_edsl_version:
720
919
  from edsl import __version__
721
920
 
@@ -735,7 +934,18 @@ class Agent(Base):
735
934
  Agent(name = \"""Steve\""", traits = {'age': 10, 'hair': 'brown', 'height': 5.5})
736
935
 
737
936
  """
738
- return cls(**agent_dict)
937
+ if "traits" in agent_dict:
938
+ return cls(
939
+ traits=agent_dict["traits"],
940
+ name=agent_dict.get("name", None),
941
+ instruction=agent_dict.get("instruction", None),
942
+ traits_presentation_template=agent_dict.get(
943
+ "traits_presentation_template", None
944
+ ),
945
+ codebook=agent_dict.get("codebook", None),
946
+ )
947
+ else: # old-style agent - we used to only store the traits
948
+ return cls(**agent_dict)
739
949
 
740
950
  def _table(self) -> tuple[dict, list]:
741
951
  """Prepare generic table data."""
@@ -746,10 +956,15 @@ class Agent(Base):
746
956
  return table_data, column_names
747
957
 
748
958
  def add_trait(self, trait_name_or_dict: str, value: Optional[Any] = None) -> Agent:
749
- """Adds a trait to an agent and returns that agent"""
959
+ """Adds a trait to an agent and returns that agent
960
+ >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
961
+ >>> a.add_trait("weight", 150)
962
+ Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5, 'weight': 150})
963
+ """
750
964
  if isinstance(trait_name_or_dict, dict) and value is None:
751
- self.traits.update(trait_name_or_dict)
752
- return self
965
+ newagent = self.duplicate()
966
+ newagent.traits = {**self.traits, **trait_name_or_dict}
967
+ return newagent
753
968
 
754
969
  if isinstance(trait_name_or_dict, dict) and value:
755
970
  raise AgentErrors(
@@ -757,9 +972,9 @@ class Agent(Base):
757
972
  )
758
973
 
759
974
  if isinstance(trait_name_or_dict, str):
760
- trait = trait_name_or_dict
761
- self.traits[trait] = value
762
- return self
975
+ newagent = self.duplicate()
976
+ newagent.traits = {**self.traits, **{trait_name_or_dict: value}}
977
+ return newagent
763
978
 
764
979
  raise AgentErrors("Something is not right with adding a trait to an Agent")
765
980
 
@@ -772,8 +987,9 @@ class Agent(Base):
772
987
  >>> a.remove_trait("age")
773
988
  Agent(traits = {'hair': 'brown', 'height': 5.5})
774
989
  """
775
- _ = self.traits.pop(trait)
776
- return self
990
+ newagent = self.duplicate()
991
+ newagent.traits = {k: v for k, v in self.traits.items() if k != trait}
992
+ return newagent
777
993
 
778
994
  def translate_traits(self, values_codebook: dict) -> Agent:
779
995
  """Translate traits to a new codebook.
@@ -784,32 +1000,15 @@ class Agent(Base):
784
1000
 
785
1001
  :param values_codebook: The new codebook.
786
1002
  """
1003
+ new_traits = {}
787
1004
  for key, value in self.traits.items():
788
1005
  if key in values_codebook:
789
- self.traits[key] = values_codebook[key][value]
790
- return self
791
-
792
- def rich_print(self):
793
- """Display an object as a rich table.
794
-
795
- Example usage:
796
-
797
- >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
798
- >>> a.rich_print()
799
- <rich.table.Table object at ...>
800
- """
801
- from rich.table import Table
802
-
803
- table_data, column_names = self._table()
804
- table = Table(title=f"{self.__class__.__name__} Attributes")
805
- for column in column_names:
806
- table.add_column(column, style="bold")
807
-
808
- for row in table_data:
809
- row_data = [row[column] for column in column_names]
810
- table.add_row(*row_data)
811
-
812
- return table
1006
+ new_traits[key] = values_codebook[key].get(value, value)
1007
+ else:
1008
+ new_traits[key] = value
1009
+ newagent = self.duplicate()
1010
+ newagent.traits = new_traits
1011
+ return newagent
813
1012
 
814
1013
  @classmethod
815
1014
  def example(cls, randomize: bool = False) -> Agent:
@@ -817,6 +1016,9 @@ class Agent(Base):
817
1016
  Returns an example Agent instance.
818
1017
 
819
1018
  :param randomize: If True, adds a random string to the value of an example key.
1019
+
1020
+ >>> Agent.example()
1021
+ Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})
820
1022
  """
821
1023
  addition = "" if not randomize else str(uuid4())
822
1024
  return cls(traits={"age": 22, "hair": f"brown{addition}", "height": 5.5})
@@ -828,10 +1030,12 @@ class Agent(Base):
828
1030
 
829
1031
  >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
830
1032
  >>> print(a.code())
831
- from edsl import Agent
1033
+ from edsl.agents.Agent import Agent
832
1034
  agent = Agent(traits={'age': 10, 'hair': 'brown', 'height': 5.5})
833
1035
  """
834
- return f"from edsl import Agent\nagent = Agent(traits={self.traits})"
1036
+ return (
1037
+ f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
1038
+ )
835
1039
 
836
1040
 
837
1041
  def main():