edsl 0.1.29.dev3__py3-none-any.whl → 0.1.30__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 (75) hide show
  1. edsl/Base.py +18 -18
  2. edsl/__init__.py +23 -23
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +79 -41
  5. edsl/agents/AgentList.py +26 -26
  6. edsl/agents/Invigilator.py +19 -2
  7. edsl/agents/InvigilatorBase.py +15 -10
  8. edsl/agents/PromptConstructionMixin.py +342 -100
  9. edsl/agents/descriptors.py +2 -1
  10. edsl/base/Base.py +289 -0
  11. edsl/config.py +2 -1
  12. edsl/conjure/InputData.py +39 -8
  13. edsl/conversation/car_buying.py +1 -1
  14. edsl/coop/coop.py +187 -150
  15. edsl/coop/utils.py +43 -75
  16. edsl/data/Cache.py +41 -18
  17. edsl/data/CacheEntry.py +6 -7
  18. edsl/data/SQLiteDict.py +11 -3
  19. edsl/data_transfer_models.py +4 -0
  20. edsl/jobs/Answers.py +15 -1
  21. edsl/jobs/Jobs.py +108 -49
  22. edsl/jobs/buckets/ModelBuckets.py +14 -2
  23. edsl/jobs/buckets/TokenBucket.py +32 -5
  24. edsl/jobs/interviews/Interview.py +99 -79
  25. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +19 -24
  26. edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
  27. edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
  28. edsl/jobs/tasks/TaskHistory.py +4 -3
  29. edsl/language_models/LanguageModel.py +17 -17
  30. edsl/language_models/ModelList.py +1 -1
  31. edsl/language_models/repair.py +8 -7
  32. edsl/notebooks/Notebook.py +47 -10
  33. edsl/prompts/Prompt.py +31 -19
  34. edsl/questions/QuestionBase.py +38 -13
  35. edsl/questions/QuestionBudget.py +5 -6
  36. edsl/questions/QuestionCheckBox.py +7 -3
  37. edsl/questions/QuestionExtract.py +5 -3
  38. edsl/questions/QuestionFreeText.py +7 -5
  39. edsl/questions/QuestionFunctional.py +34 -5
  40. edsl/questions/QuestionList.py +3 -4
  41. edsl/questions/QuestionMultipleChoice.py +68 -12
  42. edsl/questions/QuestionNumerical.py +4 -3
  43. edsl/questions/QuestionRank.py +5 -3
  44. edsl/questions/__init__.py +4 -3
  45. edsl/questions/descriptors.py +46 -4
  46. edsl/questions/question_registry.py +20 -31
  47. edsl/questions/settings.py +1 -1
  48. edsl/results/Dataset.py +31 -0
  49. edsl/results/DatasetExportMixin.py +570 -0
  50. edsl/results/Result.py +66 -70
  51. edsl/results/Results.py +160 -68
  52. edsl/results/ResultsDBMixin.py +7 -3
  53. edsl/results/ResultsExportMixin.py +22 -537
  54. edsl/results/ResultsGGMixin.py +3 -3
  55. edsl/results/ResultsToolsMixin.py +5 -5
  56. edsl/scenarios/FileStore.py +299 -0
  57. edsl/scenarios/Scenario.py +16 -24
  58. edsl/scenarios/ScenarioList.py +42 -17
  59. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  60. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  61. edsl/scenarios/__init__.py +1 -0
  62. edsl/study/Study.py +8 -16
  63. edsl/surveys/MemoryPlan.py +11 -4
  64. edsl/surveys/Survey.py +88 -17
  65. edsl/surveys/SurveyExportMixin.py +4 -2
  66. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  67. edsl/tools/plotting.py +4 -2
  68. edsl/utilities/__init__.py +21 -21
  69. edsl/utilities/interface.py +66 -45
  70. edsl/utilities/utilities.py +11 -13
  71. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/METADATA +11 -10
  72. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/RECORD +74 -71
  73. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
  74. edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
  75. {edsl-0.1.29.dev3.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
edsl/Base.py CHANGED
@@ -6,9 +6,6 @@ import io
6
6
  import json
7
7
  from typing import Any, Optional, Union
8
8
  from uuid import UUID
9
- from IPython.display import display
10
- from rich.console import Console
11
- from edsl.utilities import is_notebook
12
9
 
13
10
 
14
11
  class RichPrintingMixin:
@@ -16,6 +13,8 @@ class RichPrintingMixin:
16
13
 
17
14
  def _for_console(self):
18
15
  """Return a string representation of the object for console printing."""
16
+ from rich.console import Console
17
+
19
18
  with io.StringIO() as buf:
20
19
  console = Console(file=buf, record=True)
21
20
  table = self.rich_print()
@@ -28,7 +27,11 @@ class RichPrintingMixin:
28
27
 
29
28
  def print(self):
30
29
  """Print the object to the console."""
30
+ from edsl.utilities.utilities import is_notebook
31
+
31
32
  if is_notebook():
33
+ from IPython.display import display
34
+
32
35
  display(self.rich_print())
33
36
  else:
34
37
  from rich.console import Console
@@ -52,31 +55,28 @@ class PersistenceMixin:
52
55
  return c.create(self, description, visibility)
53
56
 
54
57
  @classmethod
55
- def pull(cls, id_or_url: Union[str, UUID], exec_profile=None):
58
+ def pull(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
56
59
  """Pull the object from coop."""
57
60
  from edsl.coop import Coop
61
+ from edsl.coop.utils import ObjectRegistry
58
62
 
59
- if id_or_url.startswith("http"):
60
- uuid_value = id_or_url.split("/")[-1]
61
- else:
62
- uuid_value = id_or_url
63
-
64
- c = Coop()
65
-
66
- return c._get_base(cls, uuid_value, exec_profile=exec_profile)
63
+ object_type = ObjectRegistry.get_object_type_by_edsl_class(cls)
64
+ coop = Coop()
65
+ return coop.get(uuid, url, object_type)
67
66
 
68
67
  @classmethod
69
- def delete(cls, id_or_url: Union[str, UUID]):
68
+ def delete(cls, uuid: Optional[Union[str, UUID]] = None, url: Optional[str] = None):
70
69
  """Delete the object from coop."""
71
70
  from edsl.coop import Coop
72
71
 
73
- c = Coop()
74
- return c._delete_base(cls, id_or_url)
72
+ coop = Coop()
73
+ return coop.delete(uuid, url)
75
74
 
76
75
  @classmethod
77
76
  def patch(
78
77
  cls,
79
- id_or_url: Union[str, UUID],
78
+ uuid: Optional[Union[str, UUID]] = None,
79
+ url: Optional[str] = None,
80
80
  description: Optional[str] = None,
81
81
  value: Optional[Any] = None,
82
82
  visibility: Optional[str] = None,
@@ -89,8 +89,8 @@ class PersistenceMixin:
89
89
  """
90
90
  from edsl.coop import Coop
91
91
 
92
- c = Coop()
93
- return c._patch_base(cls, id_or_url, description, value, visibility)
92
+ coop = Coop()
93
+ return coop.patch(uuid, url, description, value, visibility)
94
94
 
95
95
  @classmethod
96
96
  def search(cls, query):
edsl/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import time
2
3
 
3
4
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
4
5
  ROOT_DIR = os.path.dirname(BASE_DIR)
@@ -7,36 +8,35 @@ from edsl.__version__ import __version__
7
8
  from edsl.config import Config, CONFIG
8
9
  from edsl.agents.Agent import Agent
9
10
  from edsl.agents.AgentList import AgentList
10
- from edsl.questions import (
11
- QuestionBase,
12
- QuestionBudget,
13
- QuestionCheckBox,
14
- QuestionExtract,
15
- QuestionFreeText,
16
- QuestionFunctional,
17
- QuestionLikertFive,
18
- QuestionList,
19
- QuestionLinearScale,
20
- QuestionMultipleChoice,
21
- QuestionNumerical,
22
- QuestionRank,
23
- QuestionTopK,
24
- QuestionYesNo,
25
- )
26
- from edsl.scenarios.Scenario import Scenario
27
- from edsl.scenarios.ScenarioList import ScenarioList
28
- from edsl.utilities.interface import print_dict_with_rich
11
+ from edsl.questions import QuestionBase
12
+ from edsl.questions import QuestionMultipleChoice
13
+ from edsl.questions import QuestionBudget
14
+ from edsl.questions import QuestionCheckBox
15
+ from edsl.questions import QuestionExtract
16
+ from edsl.questions import QuestionFreeText
17
+ from edsl.questions import QuestionFunctional
18
+ from edsl.questions import QuestionLikertFive
19
+ from edsl.questions import QuestionList
20
+ from edsl.questions import QuestionLinearScale
21
+ from edsl.questions import QuestionNumerical
22
+ from edsl.questions import QuestionRank
23
+ from edsl.questions import QuestionTopK
24
+ from edsl.questions import QuestionYesNo
25
+ from edsl.questions.question_registry import Question
26
+ from edsl.scenarios import Scenario
27
+ from edsl.scenarios import ScenarioList
28
+
29
+ # from edsl.utilities.interface import print_dict_with_rich
29
30
  from edsl.surveys.Survey import Survey
30
31
  from edsl.language_models.registry import Model
31
- from edsl.questions.question_registry import Question
32
+ from edsl.language_models.ModelList import ModelList
32
33
  from edsl.results.Results import Results
33
34
  from edsl.data.Cache import Cache
34
35
  from edsl.data.CacheEntry import CacheEntry
35
36
  from edsl.data.CacheHandler import set_session_cache, unset_session_cache
36
37
  from edsl.shared import shared_globals
37
- from edsl.jobs import Jobs
38
- from edsl.notebooks import Notebook
38
+ from edsl.jobs.Jobs import Jobs
39
+ from edsl.notebooks.Notebook import Notebook
39
40
  from edsl.study.Study import Study
40
41
  from edsl.conjure.Conjure import Conjure
41
- from edsl.language_models.ModelList import ModelList
42
42
  from edsl.coop.coop import Coop
edsl/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.29.dev3"
1
+ __version__ = "0.1.30"
edsl/agents/Agent.py CHANGED
@@ -4,28 +4,16 @@ from __future__ import annotations
4
4
  import copy
5
5
  import inspect
6
6
  import types
7
- from typing import Any, Callable, Optional, Union, Dict, Sequence
8
-
9
- from rich.table import Table
10
-
7
+ from typing import Callable, Optional, Union
8
+ from uuid import uuid4
11
9
  from edsl.Base import Base
12
- from edsl.questions.QuestionBase import QuestionBase
13
- from edsl.language_models import LanguageModel
14
- from edsl.surveys.MemoryPlan import MemoryPlan
10
+
15
11
  from edsl.exceptions.agents import (
16
12
  AgentCombinationError,
17
13
  AgentDirectAnswerFunctionError,
18
14
  AgentDynamicTraitsFunctionError,
19
15
  )
20
- from edsl.agents.Invigilator import (
21
- InvigilatorDebug,
22
- InvigilatorHuman,
23
- InvigilatorFunctional,
24
- InvigilatorAI,
25
- InvigilatorBase,
26
- )
27
- from edsl.language_models.registry import Model
28
- from edsl.scenarios import Scenario
16
+
29
17
  from edsl.agents.descriptors import (
30
18
  TraitsDescriptor,
31
19
  CodebookDescriptor,
@@ -38,10 +26,6 @@ from edsl.utilities.decorators import (
38
26
  remove_edsl_version,
39
27
  )
40
28
  from edsl.data_transfer_models import AgentResponseDict
41
- from edsl.prompts.library.agent_persona import AgentPersona
42
- from edsl.data.Cache import Cache
43
-
44
-
45
29
  from edsl.utilities.restricted_python import create_restricted_function
46
30
 
47
31
 
@@ -56,6 +40,7 @@ class Agent(Base):
56
40
  name = NameDescriptor()
57
41
  dynamic_traits_function_name = ""
58
42
  answer_question_directly_function_name = ""
43
+ has_dynamic_traits_function = False
59
44
 
60
45
  def __init__(
61
46
  self,
@@ -129,12 +114,16 @@ class Agent(Base):
129
114
 
130
115
  if self.dynamic_traits_function:
131
116
  self.dynamic_traits_function_name = self.dynamic_traits_function.__name__
117
+ self.has_dynamic_traits_function = True
118
+ else:
119
+ self.has_dynamic_traits_function = False
132
120
 
133
121
  if dynamic_traits_function_source_code:
134
122
  self.dynamic_traits_function_name = dynamic_traits_function_name
135
123
  self.dynamic_traits_function = create_restricted_function(
136
124
  dynamic_traits_function_name, dynamic_traits_function
137
125
  )
126
+
138
127
  if answer_question_directly_source_code:
139
128
  self.answer_question_directly_function_name = (
140
129
  answer_question_directly_function_name
@@ -151,6 +140,8 @@ class Agent(Base):
151
140
  self.current_question = None
152
141
 
153
142
  if traits_presentation_template is not None:
143
+ from edsl.prompts.library.agent_persona import AgentPersona
144
+
154
145
  self.traits_presentation_template = traits_presentation_template
155
146
  self.agent_persona = AgentPersona(text=self.traits_presentation_template)
156
147
 
@@ -159,7 +150,7 @@ class Agent(Base):
159
150
 
160
151
  This checks whether the dynamic traits function is valid.
161
152
  """
162
- if self.dynamic_traits_function is not None:
153
+ if self.has_dynamic_traits_function:
163
154
  sig = inspect.signature(self.dynamic_traits_function)
164
155
  if "question" in sig.parameters:
165
156
  if len(sig.parameters) > 1:
@@ -189,7 +180,7 @@ class Agent(Base):
189
180
  {'age': 10, 'hair': 'brown', 'height': 5.5}
190
181
 
191
182
  """
192
- if self.dynamic_traits_function is not None:
183
+ if self.has_dynamic_traits_function:
193
184
  sig = inspect.signature(self.dynamic_traits_function)
194
185
  if "question" in sig.parameters:
195
186
  return self.dynamic_traits_function(question=self.current_question)
@@ -271,8 +262,9 @@ class Agent(Base):
271
262
  def create_invigilator(
272
263
  self,
273
264
  *,
274
- question: QuestionBase,
275
- cache,
265
+ question: "QuestionBase",
266
+ cache: "Cache",
267
+ survey: Optional["Survey"] = None,
276
268
  scenario: Optional[Scenario] = None,
277
269
  model: Optional[LanguageModel] = None,
278
270
  debug: bool = False,
@@ -280,7 +272,7 @@ class Agent(Base):
280
272
  current_answers: Optional[dict] = None,
281
273
  iteration: int = 1,
282
274
  sidecar_model=None,
283
- ) -> InvigilatorBase:
275
+ ) -> "InvigilatorBase":
284
276
  """Create an Invigilator.
285
277
 
286
278
  An invigilator is an object that is responsible for administering a question to an agent.
@@ -294,6 +286,8 @@ class Agent(Base):
294
286
  An invigator is an object that is responsible for administering a question to an agent and
295
287
  recording the responses.
296
288
  """
289
+ from edsl import Model, Scenario
290
+
297
291
  cache = cache
298
292
  self.current_question = question
299
293
  model = model or Model()
@@ -301,6 +295,7 @@ class Agent(Base):
301
295
  invigilator = self._create_invigilator(
302
296
  question=question,
303
297
  scenario=scenario,
298
+ survey=survey,
304
299
  model=model,
305
300
  debug=debug,
306
301
  memory_plan=memory_plan,
@@ -314,12 +309,13 @@ class Agent(Base):
314
309
  async def async_answer_question(
315
310
  self,
316
311
  *,
317
- question: QuestionBase,
318
- cache: Cache,
319
- scenario: Optional[Scenario] = None,
320
- model: Optional[LanguageModel] = None,
312
+ question: "QuestionBase",
313
+ cache: "Cache",
314
+ scenario: Optional["Scenario"] = None,
315
+ survey: Optional["Survey"] = None,
316
+ model: Optional["LanguageModel"] = None,
321
317
  debug: bool = False,
322
- memory_plan: Optional[MemoryPlan] = None,
318
+ memory_plan: Optional["MemoryPlan"] = None,
323
319
  current_answers: Optional[dict] = None,
324
320
  iteration: int = 0,
325
321
  ) -> AgentResponseDict:
@@ -349,6 +345,7 @@ class Agent(Base):
349
345
  question=question,
350
346
  cache=cache,
351
347
  scenario=scenario,
348
+ survey=survey,
352
349
  model=model,
353
350
  debug=debug,
354
351
  memory_plan=memory_plan,
@@ -362,21 +359,35 @@ class Agent(Base):
362
359
 
363
360
  def _create_invigilator(
364
361
  self,
365
- question: QuestionBase,
366
- cache: Optional[Cache] = None,
367
- scenario: Optional[Scenario] = None,
368
- model: Optional[LanguageModel] = None,
362
+ question: "QuestionBase",
363
+ cache: Optional["Cache"] = None,
364
+ scenario: Optional["Scenario"] = None,
365
+ model: Optional["LanguageModel"] = None,
366
+ survey: Optional["Survey"] = None,
369
367
  debug: bool = False,
370
- memory_plan: Optional[MemoryPlan] = None,
368
+ memory_plan: Optional["MemoryPlan"] = None,
371
369
  current_answers: Optional[dict] = None,
372
370
  iteration: int = 0,
373
371
  sidecar_model=None,
374
- ) -> InvigilatorBase:
372
+ ) -> "InvigilatorBase":
375
373
  """Create an Invigilator."""
374
+ from edsl import Model
375
+ from edsl import Scenario
376
+
376
377
  model = model or Model()
377
378
  scenario = scenario or Scenario()
378
379
 
380
+ from edsl.agents.Invigilator import (
381
+ InvigilatorDebug,
382
+ InvigilatorHuman,
383
+ InvigilatorFunctional,
384
+ InvigilatorAI,
385
+ InvigilatorBase,
386
+ )
387
+
379
388
  if cache is None:
389
+ from edsl.data.Cache import Cache
390
+
380
391
  cache = Cache()
381
392
 
382
393
  if debug:
@@ -404,6 +415,7 @@ class Agent(Base):
404
415
  self,
405
416
  question=question,
406
417
  scenario=scenario,
418
+ survey=survey,
407
419
  model=model,
408
420
  memory_plan=memory_plan,
409
421
  current_answers=current_answers,
@@ -479,6 +491,29 @@ class Agent(Base):
479
491
  """
480
492
  return self.data == other.data
481
493
 
494
+ def __getattr__(self, name):
495
+ # This will be called only if 'name' is not found in the usual places
496
+ # breakpoint()
497
+ if name == "has_dynamic_traits_function":
498
+ return self.has_dynamic_traits_function
499
+
500
+ if name in self.traits:
501
+ return self.traits[name]
502
+ raise AttributeError(
503
+ f"'{type(self).__name__}' object has no attribute '{name}'"
504
+ )
505
+
506
+ def __getstate__(self):
507
+ state = self.__dict__.copy()
508
+ # Include any additional state that needs to be serialized
509
+ return state
510
+
511
+ def __setstate__(self, state):
512
+ self.__dict__.update(state)
513
+ # Ensure _traits is initialized if it's missing
514
+ if "_traits" not in self.__dict__:
515
+ self._traits = {}
516
+
482
517
  def print(self) -> None:
483
518
  from rich import print_json
484
519
  import json
@@ -640,6 +675,8 @@ class Agent(Base):
640
675
  >>> a.rich_print()
641
676
  <rich.table.Table object at ...>
642
677
  """
678
+ from rich.table import Table
679
+
643
680
  table_data, column_names = self._table()
644
681
  table = Table(title=f"{self.__class__.__name__} Attributes")
645
682
  for column in column_names:
@@ -652,13 +689,14 @@ class Agent(Base):
652
689
  return table
653
690
 
654
691
  @classmethod
655
- def example(cls) -> Agent:
656
- """Return an example agent.
692
+ def example(cls, randomize: bool = False) -> Agent:
693
+ """
694
+ Returns an example Agent instance.
657
695
 
658
- >>> Agent.example()
659
- Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})
696
+ :param randomize: If True, adds a random string to the value of an example key.
660
697
  """
661
- return cls(traits={"age": 22, "hair": "brown", "height": 5.5})
698
+ addition = "" if not randomize else str(uuid4())
699
+ return cls(traits={"age": 22, "hair": f"brown{addition}", "height": 5.5})
662
700
 
663
701
  def code(self) -> str:
664
702
  """Return the code for the agent.
edsl/agents/AgentList.py CHANGED
@@ -11,28 +11,21 @@ Example usage:
11
11
  """
12
12
 
13
13
  from __future__ import annotations
14
+ import csv
15
+ import json
14
16
  from collections import UserList
15
- from typing import Optional, Union, Sequence, List, Any
17
+ from typing import Any, List, Optional, Union
16
18
  from rich import print_json
17
19
  from rich.table import Table
18
- import json
19
- import csv
20
-
21
-
22
20
  from simpleeval import EvalWithCompoundTypes
23
-
24
21
  from edsl.Base import Base
25
- from edsl.agents import Agent
26
- from edsl.utilities.decorators import (
27
- add_edsl_version,
28
- remove_edsl_version,
29
- )
22
+ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
30
23
 
31
24
 
32
25
  class AgentList(UserList, Base):
33
26
  """A list of Agents."""
34
27
 
35
- def __init__(self, data: Optional[list[Agent]] = None):
28
+ def __init__(self, data: Optional[list["Agent"]] = None):
36
29
  """Initialize a new AgentList.
37
30
 
38
31
  :param data: A list of Agents.
@@ -77,6 +70,7 @@ class AgentList(UserList, Base):
77
70
  def select(self, *traits) -> AgentList:
78
71
  """Selects agents with only the references traits.
79
72
 
73
+ >>> from edsl.agents.Agent import Agent
80
74
  >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
81
75
  >>> al.select('a')
82
76
  AgentList([Agent(traits = {'a': 1}), Agent(traits = {'a': 1})])
@@ -94,12 +88,13 @@ class AgentList(UserList, Base):
94
88
  """
95
89
  Filter a list of agents based on an expression.
96
90
 
91
+ >>> from edsl.agents.Agent import Agent
97
92
  >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
98
93
  >>> al.filter("b == 2")
99
94
  AgentList([Agent(traits = {'a': 1, 'b': 2})])
100
95
  """
101
96
 
102
- def create_evaluator(agent: Agent):
97
+ def create_evaluator(agent: "Agent"):
103
98
  """Create an evaluator for the given result.
104
99
  The 'combined_dict' is a mapping of all values for that Result object.
105
100
  """
@@ -133,6 +128,8 @@ class AgentList(UserList, Base):
133
128
 
134
129
  :param file_path: The path to the CSV file.
135
130
  """
131
+ from edsl.agents.Agent import Agent
132
+
136
133
  agent_list = []
137
134
  with open(file_path, "r") as f:
138
135
  reader = csv.DictReader(f)
@@ -153,7 +150,7 @@ class AgentList(UserList, Base):
153
150
  """Remove traits from the AgentList.
154
151
 
155
152
  :param traits: The traits to remove.
156
-
153
+ >>> from edsl.agents.Agent import Agent
157
154
  >>> al = AgentList([Agent({'age': 22, 'hair': 'brown', 'height': 5.5}), Agent({'age': 22, 'hair': 'brown', 'height': 5.5})])
158
155
  >>> al.remove_trait('age')
159
156
  AgentList([Agent(traits = {'hair': 'brown', 'height': 5.5}), Agent(traits = {'hair': 'brown', 'height': 5.5})])
@@ -222,35 +219,39 @@ class AgentList(UserList, Base):
222
219
  """Deserialize the dictionary back to an AgentList object.
223
220
 
224
221
  :param: data: A dictionary representing an AgentList.
225
-
222
+ >>> from edsl.agents.Agent import Agent
226
223
  >>> al = AgentList([Agent.example(), Agent.example()])
227
224
  >>> al2 = AgentList.from_dict(al.to_dict())
228
225
  >>> al2 == al
229
226
  True
230
227
  """
228
+ from edsl.agents.Agent import Agent
229
+
231
230
  agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
232
231
  return cls(agents)
233
232
 
234
233
  @classmethod
235
- def example(cls) -> "AgentList":
236
- """Return an example AgentList.
237
-
238
- >>> al = AgentList.example()
239
- >>> len(al)
240
- 2
234
+ def example(cls, randomize: bool = False) -> AgentList:
235
+ """
236
+ Returns an example AgentList instance.
241
237
 
238
+ :param randomize: If True, uses Agent's randomize method.
242
239
  """
243
- return cls([Agent.example(), Agent.example()])
244
-
240
+ from edsl.agents.Agent import Agent
241
+
242
+ return cls([Agent.example(randomize), Agent.example(randomize)])
243
+
245
244
  @classmethod
246
- def from_list(self, trait_name:str, values: List[Any]):
245
+ def from_list(self, trait_name: str, values: List[Any]):
247
246
  """Create an AgentList from a list of values.
248
247
 
249
248
  :param trait_name: The name of the trait.
250
249
  :param values: A list of values.
251
250
  """
251
+ from edsl.agents.Agent import Agent
252
+
252
253
  return AgentList([Agent({trait_name: value}) for value in values])
253
-
254
+
254
255
  def __mul__(self, other: AgentList) -> AgentList:
255
256
  """Takes the cross product of two AgentLists."""
256
257
  from itertools import product
@@ -260,7 +261,6 @@ class AgentList(UserList, Base):
260
261
  new_sl.append(s1 + s2)
261
262
  return AgentList(new_sl)
262
263
 
263
-
264
264
  def code(self, string=True) -> Union[str, list[str]]:
265
265
  """Return code to construct an AgentList.
266
266
 
@@ -74,15 +74,30 @@ class InvigilatorAI(PromptConstructorMixin, InvigilatorBase):
74
74
 
75
75
  This cleans up the raw response to make it suitable to pass to AgentResponseDict.
76
76
  """
77
- # not actually used, but this removes the temptation to delete agent from the signature
78
77
  _ = agent
79
78
  try:
80
79
  response = question._validate_answer(raw_response)
81
80
  except Exception as e:
81
+ """If the response is invalid, remove it from the cache and raise the exception."""
82
82
  self._remove_from_cache(raw_response)
83
83
  raise e
84
84
 
85
- answer = question._translate_answer_code_to_answer(response["answer"], scenario)
85
+ question_dict = self.survey.question_names_to_questions()
86
+ for other_question, answer in self.current_answers.items():
87
+ if other_question in question_dict:
88
+ question_dict[other_question].answer = answer
89
+ else:
90
+ # adds a comment to the question
91
+ if (
92
+ new_question := other_question.split("_comment")[0]
93
+ ) in question_dict:
94
+ question_dict[new_question].comment = answer
95
+
96
+ combined_dict = {**question_dict, **scenario}
97
+ answer = question._translate_answer_code_to_answer(
98
+ response["answer"], combined_dict
99
+ )
100
+ # breakpoint()
86
101
  data = {
87
102
  "answer": answer,
88
103
  "comment": response.get(
@@ -93,6 +108,8 @@ class InvigilatorAI(PromptConstructorMixin, InvigilatorBase):
93
108
  "cached_response": raw_response.get("cached_response", None),
94
109
  "usage": raw_response.get("usage", {}),
95
110
  "raw_model_response": raw_model_response,
111
+ "cache_used": raw_response.get("cache_used", False),
112
+ "cache_key": raw_response.get("cache_key", None),
96
113
  }
97
114
  return AgentResponseDict(**data)
98
115
 
@@ -46,6 +46,7 @@ class InvigilatorBase(ABC):
46
46
  model: LanguageModel,
47
47
  memory_plan: MemoryPlan,
48
48
  current_answers: dict,
49
+ survey: Optional["Survey"],
49
50
  cache: Optional[Cache] = None,
50
51
  iteration: Optional[int] = 1,
51
52
  additional_prompt_data: Optional[dict] = None,
@@ -57,11 +58,12 @@ class InvigilatorBase(ABC):
57
58
  self.scenario = scenario
58
59
  self.model = model
59
60
  self.memory_plan = memory_plan
60
- self.current_answers = current_answers
61
+ self.current_answers = current_answers or {}
61
62
  self.iteration = iteration
62
63
  self.additional_prompt_data = additional_prompt_data
63
64
  self.cache = cache
64
65
  self.sidecar_model = sidecar_model
66
+ self.survey = survey
65
67
 
66
68
  def __repr__(self) -> str:
67
69
  """Return a string representation of the Invigilator.
@@ -76,7 +78,7 @@ class InvigilatorBase(ABC):
76
78
  """Return an AgentResponseDict used in case the question-asking fails.
77
79
 
78
80
  >>> InvigilatorBase.example().get_failed_task_result()
79
- {'answer': None, 'comment': 'Failed to get response', 'question_name': 'how_feeling', ...}
81
+ {'answer': None, 'comment': 'Failed to get response', ...}
80
82
  """
81
83
  return AgentResponseDict(
82
84
  answer=None,
@@ -86,11 +88,8 @@ class InvigilatorBase(ABC):
86
88
  )
87
89
 
88
90
  def get_prompts(self) -> Dict[str, Prompt]:
89
- """Return the prompt used.
91
+ """Return the prompt used."""
90
92
 
91
- >>> InvigilatorBase.example().get_prompts()
92
- {'user_prompt': Prompt(text=\"""NA\"""), 'system_prompt': Prompt(text=\"""NA\""")}
93
- """
94
93
  return {
95
94
  "user_prompt": Prompt("NA"),
96
95
  "system_prompt": Prompt("NA"),
@@ -129,7 +128,7 @@ class InvigilatorBase(ABC):
129
128
  )
130
129
 
131
130
  @classmethod
132
- def example(cls, throw_an_exception=False):
131
+ def example(cls, throw_an_exception=False, question=None, scenario=None):
133
132
  """Return an example invigilator.
134
133
 
135
134
  >>> InvigilatorBase.example()
@@ -167,15 +166,20 @@ class InvigilatorBase(ABC):
167
166
  if throw_an_exception:
168
167
  model.throw_an_exception = True
169
168
  agent = Agent.example()
170
- question = QuestionMultipleChoice.example()
171
- scenario = Scenario.example()
169
+ # question = QuestionMultipleChoice.example()
170
+ from edsl.surveys import Survey
171
+
172
+ survey = Survey.example()
173
+ question = question or survey.questions[0]
174
+ scenario = scenario or Scenario.example()
172
175
  # memory_plan = None #memory_plan = MemoryPlan()
173
176
  from edsl import Survey
174
177
 
175
178
  memory_plan = MemoryPlan(survey=Survey.example())
176
179
  current_answers = None
180
+ from edsl.agents.PromptConstructionMixin import PromptConstructorMixin
177
181
 
178
- class InvigilatorExample(InvigilatorBase):
182
+ class InvigilatorExample(PromptConstructorMixin, InvigilatorBase):
179
183
  """An example invigilator."""
180
184
 
181
185
  async def async_answer_question(self):
@@ -188,6 +192,7 @@ class InvigilatorBase(ABC):
188
192
  agent=agent,
189
193
  question=question,
190
194
  scenario=scenario,
195
+ survey=survey,
191
196
  model=model,
192
197
  memory_plan=memory_plan,
193
198
  current_answers=current_answers,