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.
- edsl/Base.py +197 -116
- edsl/__init__.py +15 -7
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +351 -147
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +101 -50
- edsl/agents/InvigilatorBase.py +62 -70
- edsl/agents/PromptConstructor.py +143 -225
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +18 -5
- edsl/auto/StageBase.py +53 -40
- edsl/auto/StageQuestions.py +2 -1
- edsl/auto/utilities.py +0 -6
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +125 -47
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +45 -27
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +31 -12
- edsl/data/RemoteCacheSync.py +154 -46
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +27 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +38 -19
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +7 -12
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +120 -79
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +47 -35
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -10
- edsl/inference_services/TogetherAIService.py +5 -3
- edsl/inference_services/data_structures.py +134 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +356 -431
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +6 -4
- edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +143 -408
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
- edsl/jobs/runners/JobsRunnerStatus.py +133 -165
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +38 -18
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +194 -236
- edsl/language_models/ModelList.py +28 -19
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +1 -2
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +68 -214
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionBudget.py +1 -1
- edsl/questions/QuestionCheckBox.py +3 -3
- edsl/questions/QuestionExtract.py +5 -7
- edsl/questions/QuestionFreeText.py +2 -3
- edsl/questions/QuestionList.py +10 -18
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +67 -23
- edsl/questions/QuestionNumerical.py +2 -4
- edsl/questions/QuestionRank.py +7 -17
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +2 -1
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
- edsl/questions/data_structures.py +20 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
- edsl/questions/question_registry.py +1 -1
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +168 -305
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +298 -206
- edsl/results/Results.py +149 -131
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/{Selector.py → results_selector.py} +23 -13
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +150 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioList.py +415 -244
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +270 -791
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
- edsl-0.1.39.dist-info/RECORD +358 -0
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.38.dev4.dist-info/RECORD +0 -277
- /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
- /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
- /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
- {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
|
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
|
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.
|
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
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|
-
|
253
|
-
|
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,
|
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")
|
402
|
+
>>> newa = a.rename("age", "years")
|
403
|
+
>>> newa == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
|
264
404
|
True
|
265
405
|
|
266
|
-
>>>
|
267
|
-
|
406
|
+
>>> newa.rename({'years': 'smage'}) == Agent(traits = {'smage': 10, 'hair': 'brown', 'height': 5.5})
|
407
|
+
True
|
268
408
|
|
269
409
|
"""
|
270
|
-
|
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.
|
293
|
-
|
451
|
+
>>> a._rename(old_name="age", new_name="years")
|
452
|
+
Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
|
453
|
+
|
294
454
|
"""
|
295
|
-
|
296
|
-
|
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:
|
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
|
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
|
-
|
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("
|
550
|
-
Agent(traits = {'
|
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
|
-
|
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
|
-
|
587
|
-
|
588
|
-
|
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
|
-
|
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
|
-
|
683
|
-
|
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
|
-
|
699
|
-
|
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
|
-
{'
|
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 =
|
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
|
-
|
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.
|
752
|
-
|
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
|
-
|
761
|
-
|
762
|
-
return
|
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
|
-
|
776
|
-
|
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
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
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
|
1036
|
+
return (
|
1037
|
+
f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
|
1038
|
+
)
|
835
1039
|
|
836
1040
|
|
837
1041
|
def main():
|