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