edsl 0.1.37__py3-none-any.whl → 0.1.37.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 (46) hide show
  1. edsl/__version__.py +1 -1
  2. edsl/agents/Agent.py +35 -86
  3. edsl/agents/AgentList.py +0 -5
  4. edsl/agents/InvigilatorBase.py +23 -2
  5. edsl/agents/PromptConstructor.py +105 -148
  6. edsl/agents/descriptors.py +4 -17
  7. edsl/conjure/AgentConstructionMixin.py +3 -11
  8. edsl/conversation/Conversation.py +14 -66
  9. edsl/coop/coop.py +14 -148
  10. edsl/data/Cache.py +1 -1
  11. edsl/exceptions/__init__.py +3 -7
  12. edsl/exceptions/agents.py +19 -17
  13. edsl/exceptions/results.py +8 -11
  14. edsl/exceptions/surveys.py +10 -13
  15. edsl/inference_services/AwsBedrock.py +2 -7
  16. edsl/inference_services/InferenceServicesCollection.py +9 -32
  17. edsl/jobs/Jobs.py +71 -306
  18. edsl/jobs/interviews/InterviewExceptionEntry.py +1 -5
  19. edsl/jobs/tasks/TaskHistory.py +0 -1
  20. edsl/language_models/LanguageModel.py +59 -47
  21. edsl/language_models/__init__.py +0 -1
  22. edsl/prompts/Prompt.py +4 -11
  23. edsl/questions/QuestionBase.py +13 -53
  24. edsl/questions/QuestionBasePromptsMixin.py +33 -1
  25. edsl/questions/QuestionFreeText.py +0 -1
  26. edsl/questions/QuestionFunctional.py +2 -2
  27. edsl/questions/descriptors.py +28 -23
  28. edsl/results/DatasetExportMixin.py +1 -25
  29. edsl/results/Result.py +1 -16
  30. edsl/results/Results.py +120 -31
  31. edsl/results/ResultsDBMixin.py +1 -1
  32. edsl/results/Selector.py +1 -18
  33. edsl/scenarios/Scenario.py +12 -48
  34. edsl/scenarios/ScenarioHtmlMixin.py +2 -7
  35. edsl/scenarios/ScenarioList.py +1 -12
  36. edsl/surveys/Rule.py +4 -10
  37. edsl/surveys/Survey.py +77 -100
  38. edsl/utilities/utilities.py +0 -18
  39. {edsl-0.1.37.dist-info → edsl-0.1.37.dev1.dist-info}/METADATA +1 -1
  40. {edsl-0.1.37.dist-info → edsl-0.1.37.dev1.dist-info}/RECORD +42 -46
  41. edsl/conversation/chips.py +0 -95
  42. edsl/exceptions/BaseException.py +0 -21
  43. edsl/exceptions/scenarios.py +0 -22
  44. edsl/language_models/KeyLookup.py +0 -30
  45. {edsl-0.1.37.dist-info → edsl-0.1.37.dev1.dist-info}/LICENSE +0 -0
  46. {edsl-0.1.37.dist-info → edsl-0.1.37.dev1.dist-info}/WHEEL +0 -0
edsl/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.37"
1
+ __version__ = "0.1.37.dev1"
edsl/agents/Agent.py CHANGED
@@ -4,23 +4,14 @@ from __future__ import annotations
4
4
  import copy
5
5
  import inspect
6
6
  import types
7
- from typing import Callable, Optional, Union, Any, TYPE_CHECKING
8
-
9
- if TYPE_CHECKING:
10
- from edsl import Cache, Survey, Scenario
11
- from edsl.language_models import LanguageModel
12
- from edsl.surveys.MemoryPlan import MemoryPlan
13
- from edsl.questions import QuestionBase
14
- from edsl.agents.Invigilator import InvigilatorBase
15
-
7
+ from typing import Callable, Optional, Union, Any
16
8
  from uuid import uuid4
17
-
18
9
  from edsl.Base import Base
10
+
19
11
  from edsl.prompts import Prompt
20
12
  from edsl.exceptions import QuestionScenarioRenderError
21
13
 
22
14
  from edsl.exceptions.agents import (
23
- AgentErrors,
24
15
  AgentCombinationError,
25
16
  AgentDirectAnswerFunctionError,
26
17
  AgentDynamicTraitsFunctionError,
@@ -42,9 +33,7 @@ from edsl.utilities.restricted_python import create_restricted_function
42
33
 
43
34
 
44
35
  class Agent(Base):
45
- """An class representing an agent that can answer questions."""
46
-
47
- __doc__ = "https://docs.expectedparrot.com/en/latest/agents.html"
36
+ """An Agent that can answer questions."""
48
37
 
49
38
  default_instruction = """You are answering questions as if you were a human. Do not break character."""
50
39
 
@@ -159,10 +148,9 @@ class Agent(Base):
159
148
  self.current_question = None
160
149
 
161
150
  if traits_presentation_template is not None:
162
- self._traits_presentation_template = traits_presentation_template
163
151
  self.traits_presentation_template = traits_presentation_template
164
152
  else:
165
- self.traits_presentation_template = "Your traits: {{traits}}"
153
+ self.traits_presentation_template = """Your traits: {{ traits }}"""
166
154
 
167
155
  @property
168
156
  def agent_persona(self) -> Prompt:
@@ -193,23 +181,13 @@ class Agent(Base):
193
181
  """Check whether dynamic trait function is valid.
194
182
 
195
183
  This checks whether the dynamic traits function is valid.
196
-
197
- >>> def f(question): return {"age": 10, "hair": "brown", "height": 5.5}
198
- >>> a = Agent(dynamic_traits_function = f)
199
- >>> a._check_dynamic_traits_function()
200
-
201
- >>> def g(question, poo): return {"age": 10, "hair": "brown", "height": 5.5}
202
- >>> a = Agent(dynamic_traits_function = g)
203
- Traceback (most recent call last):
204
- ...
205
- edsl.exceptions.agents.AgentDynamicTraitsFunctionError: ...
206
184
  """
207
185
  if self.has_dynamic_traits_function:
208
186
  sig = inspect.signature(self.dynamic_traits_function)
209
187
  if "question" in sig.parameters:
210
188
  if len(sig.parameters) > 1:
211
189
  raise AgentDynamicTraitsFunctionError(
212
- message=f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
190
+ f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
213
191
  )
214
192
  else:
215
193
  if len(sig.parameters) > 0:
@@ -243,38 +221,7 @@ class Agent(Base):
243
221
  else:
244
222
  return self._traits
245
223
 
246
- def rename(
247
- self, old_name_or_dict: Union[str, dict], new_name: Optional[str] = None
248
- ) -> Agent:
249
- """Rename a trait.
250
-
251
- Example usage:
252
-
253
- >>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
254
- >>> a.rename("age", "years") == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
255
- True
256
-
257
- >>> a.rename({'years': 'smage'})
258
- Agent(traits = {'hair': 'brown', 'height': 5.5, 'smage': 10})
259
-
260
- """
261
- if isinstance(old_name_or_dict, dict) and new_name is None:
262
- for old_name, new_name in old_name_or_dict.items():
263
- self = self._rename(old_name, new_name)
264
- return self
265
-
266
- if isinstance(old_name_or_dict, dict) and new_name:
267
- raise AgentErrors(
268
- f"You passed a dict: {old_name_or_dict} and a new name: {new_name}. You should pass only a dict."
269
- )
270
-
271
- if isinstance(old_name_or_dict, str):
272
- self._rename(old_name_or_dict, new_name)
273
- return self
274
-
275
- raise AgentErrors("Something is not right with Agent renaming")
276
-
277
- def _rename(self, old_name: str, new_name: str) -> Agent:
224
+ def rename(self, old_name: str, new_name: str) -> Agent:
278
225
  """Rename a trait.
279
226
 
280
227
  Example usage:
@@ -320,11 +267,8 @@ class Agent(Base):
320
267
  translate_response: bool = False,
321
268
  ) -> None:
322
269
  """Add a method to the agent that can answer a particular question type.
323
- https://docs.expectedparrot.com/en/latest/agents.html#agent-direct-answering-methods
324
270
 
325
271
  :param method: A method that can answer a question directly.
326
- :param validate_response: Whether to validate the response.
327
- :param translate_response: Whether to translate the response.
328
272
 
329
273
  Example usage:
330
274
 
@@ -360,10 +304,10 @@ class Agent(Base):
360
304
  question: "QuestionBase",
361
305
  cache: "Cache",
362
306
  survey: Optional["Survey"] = None,
363
- scenario: Optional["Scenario"] = None,
364
- model: Optional["LanguageModel"] = None,
307
+ scenario: Optional[Scenario] = None,
308
+ model: Optional[LanguageModel] = None,
365
309
  debug: bool = False,
366
- memory_plan: Optional["MemoryPlan"] = None,
310
+ memory_plan: Optional[MemoryPlan] = None,
367
311
  current_answers: Optional[dict] = None,
368
312
  iteration: int = 1,
369
313
  sidecar_model=None,
@@ -410,13 +354,13 @@ class Agent(Base):
410
354
  async def async_answer_question(
411
355
  self,
412
356
  *,
413
- question: QuestionBase,
414
- cache: Cache,
415
- scenario: Optional[Scenario] = None,
416
- survey: Optional[Survey] = None,
417
- model: Optional[LanguageModel] = None,
357
+ question: "QuestionBase",
358
+ cache: "Cache",
359
+ scenario: Optional["Scenario"] = None,
360
+ survey: Optional["Survey"] = None,
361
+ model: Optional["LanguageModel"] = None,
418
362
  debug: bool = False,
419
- memory_plan: Optional[MemoryPlan] = None,
363
+ memory_plan: Optional["MemoryPlan"] = None,
420
364
  current_answers: Optional[dict] = None,
421
365
  iteration: int = 0,
422
366
  ) -> AgentResponseDict:
@@ -460,13 +404,13 @@ class Agent(Base):
460
404
 
461
405
  def _create_invigilator(
462
406
  self,
463
- question: QuestionBase,
464
- cache: Optional[Cache] = None,
465
- scenario: Optional[Scenario] = None,
466
- model: Optional[LanguageModel] = None,
467
- survey: Optional[Survey] = None,
407
+ question: "QuestionBase",
408
+ cache: Optional["Cache"] = None,
409
+ scenario: Optional["Scenario"] = None,
410
+ model: Optional["LanguageModel"] = None,
411
+ survey: Optional["Survey"] = None,
468
412
  debug: bool = False,
469
- memory_plan: Optional[MemoryPlan] = None,
413
+ memory_plan: Optional["MemoryPlan"] = None,
470
414
  current_answers: Optional[dict] = None,
471
415
  iteration: int = 0,
472
416
  sidecar_model=None,
@@ -549,6 +493,9 @@ class Agent(Base):
549
493
 
550
494
  return Agent(traits={trait: self.traits[trait] for trait in traits_to_select})
551
495
 
496
+ ################
497
+ # Dunder Methods
498
+ ################
552
499
  def __add__(self, other_agent: Optional[Agent] = None) -> Agent:
553
500
  """
554
501
  Combine two agents by joining their traits.
@@ -565,7 +512,6 @@ class Agent(Base):
565
512
  Traceback (most recent call last):
566
513
  ...
567
514
  edsl.exceptions.agents.AgentCombinationError: The agents have overlapping traits: {'age'}.
568
- ...
569
515
  """
570
516
  if other_agent is None:
571
517
  return self
@@ -594,12 +540,12 @@ class Agent(Base):
594
540
 
595
541
  def __getattr__(self, name):
596
542
  # This will be called only if 'name' is not found in the usual places
543
+ # breakpoint()
597
544
  if name == "has_dynamic_traits_function":
598
545
  return self.has_dynamic_traits_function
599
546
 
600
547
  if name in self._traits:
601
548
  return self._traits[name]
602
-
603
549
  raise AttributeError(
604
550
  f"'{type(self).__name__}' object has no attribute '{name}'"
605
551
  )
@@ -652,7 +598,6 @@ class Agent(Base):
652
598
  for k, v in self.__dict__.items()
653
599
  if k.startswith("_")
654
600
  }
655
-
656
601
  if hasattr(self, "set_instructions"):
657
602
  if not self.set_instructions:
658
603
  raw_data.pop("instruction")
@@ -677,6 +622,8 @@ class Agent(Base):
677
622
  "answer_question_directly", None
678
623
  ) # in case answer_question_directly will appear with _ in self.__dict__
679
624
  answer_question_directly_func = self.answer_question_directly
625
+ # print(answer_question_directly_func)
626
+ # print(type(answer_question_directly_func), flush=True)
680
627
 
681
628
  if (
682
629
  answer_question_directly_func
@@ -697,12 +644,12 @@ class Agent(Base):
697
644
  return dict_hash(self._to_dict())
698
645
 
699
646
  def _to_dict(self) -> dict[str, Union[dict, bool]]:
700
- """Serialize to a dictionary without EDSL info"""
647
+ """Serialize to a dictionary."""
701
648
  return self.data
702
649
 
703
650
  @add_edsl_version
704
651
  def to_dict(self) -> dict[str, Union[dict, bool]]:
705
- """Serialize to a dictionary with EDSL info.
652
+ """Serialize to a dictionary.
706
653
 
707
654
  Example usage:
708
655
 
@@ -725,6 +672,10 @@ class Agent(Base):
725
672
  """
726
673
  return cls(**agent_dict)
727
674
 
675
+ ################
676
+ # DISPLAY Methods
677
+ ################
678
+
728
679
  def _table(self) -> tuple[dict, list]:
729
680
  """Prepare generic table data."""
730
681
  table_data = []
@@ -740,16 +691,14 @@ class Agent(Base):
740
691
  return self
741
692
 
742
693
  if isinstance(trait_name_or_dict, dict) and value:
743
- raise AgentErrors(
744
- f"You passed a dict: {trait_name_or_dict} and a value: {value}. You should pass only a dict."
745
- )
694
+ raise ValueError(f"You passed a dict: {trait_name_or_dict}")
746
695
 
747
696
  if isinstance(trait_name_or_dict, str):
748
697
  trait = trait_name_or_dict
749
698
  self.traits[trait] = value
750
699
  return self
751
700
 
752
- raise AgentErrors("Something is not right with adding a trait to an Agent")
701
+ raise Exception("Something is not right with adding")
753
702
 
754
703
  def remove_trait(self, trait: str) -> Agent:
755
704
  """Remove a trait from the agent.
edsl/agents/AgentList.py CHANGED
@@ -151,11 +151,6 @@ class AgentList(UserList, Base):
151
151
  with open(file_path, "r") as f:
152
152
  reader = csv.DictReader(f)
153
153
  for row in reader:
154
- if "name" in row:
155
- import warnings
156
-
157
- warnings.warn("Using 'name' field in the CSV for the Agent name")
158
- name_field = "name"
159
154
  if name_field is not None:
160
155
  agent_name = row.pop(name_field)
161
156
  agent_list.append(Agent(traits=row, name=agent_name))
@@ -236,6 +236,24 @@ class InvigilatorBase(ABC):
236
236
  from edsl import Model
237
237
 
238
238
  model = Model("test", canned_response="SPAM!")
239
+ # class TestLanguageModelGood(LanguageModel):
240
+ # """A test language model."""
241
+
242
+ # _model_ = "test"
243
+ # _parameters_ = {"temperature": 0.5}
244
+ # _inference_service_ = InferenceServiceType.TEST.value
245
+
246
+ # async def async_execute_model_call(
247
+ # self, user_prompt: str, system_prompt: str
248
+ # ) -> dict[str, Any]:
249
+ # await asyncio.sleep(0.1)
250
+ # if hasattr(self, "throw_an_exception"):
251
+ # raise Exception("Error!")
252
+ # return {"message": """{"answer": "SPAM!"}"""}
253
+
254
+ # def parse_response(self, raw_response: dict[str, Any]) -> str:
255
+ # """Parse the response from the model."""
256
+ # return raw_response["message"]
239
257
 
240
258
  if throw_an_exception:
241
259
  model.throw_an_exception = True
@@ -245,8 +263,11 @@ class InvigilatorBase(ABC):
245
263
 
246
264
  if not survey:
247
265
  survey = Survey.example()
248
-
249
- if question not in survey.questions and question is not None:
266
+ # if question:
267
+ # need to have the focal question name in the list of names
268
+ # survey._questions[0].question_name = question.question_name
269
+ # survey.add_question(question)
270
+ if question:
250
271
  survey.add_question(question)
251
272
 
252
273
  question = question or survey.questions[0]
@@ -7,23 +7,6 @@ from edsl.prompts.Prompt import Prompt
7
7
  from edsl.agents.prompt_helpers import PromptPlan
8
8
 
9
9
 
10
- class PlaceholderAnswer:
11
- """A placeholder answer for when a question is not yet answered."""
12
-
13
- def __init__(self):
14
- self.answer = "N/A"
15
- self.comment = "Will be populated by prior answer"
16
-
17
- def __getitem__(self, index):
18
- return ""
19
-
20
- def __str__(self):
21
- return "<<PlaceholderAnswer>>"
22
-
23
- def __repr__(self):
24
- return "<<PlaceholderAnswer>>"
25
-
26
-
27
10
  def get_jinja2_variables(template_str: str) -> Set[str]:
28
11
  """
29
12
  Extracts all variable names from a Jinja2 template using Jinja2's built-in parsing.
@@ -105,20 +88,15 @@ class PromptConstructor:
105
88
  return self.agent.prompt()
106
89
 
107
90
  def prior_answers_dict(self) -> dict:
108
- # this is all questions
109
91
  d = self.survey.question_names_to_questions()
110
92
  # This attaches the answer to the question
111
- for question in d:
112
- if question in self.current_answers:
113
- d[question].answer = self.current_answers[question]
93
+ for question, answer in self.current_answers.items():
94
+ if question in d:
95
+ d[question].answer = answer
114
96
  else:
115
- d[question].answer = PlaceholderAnswer()
116
-
117
- # if (new_question := question.split("_comment")[0]) in d:
118
- # d[new_question].comment = answer
119
- # d[question].answer = PlaceholderAnswer()
120
-
121
- # breakpoint()
97
+ # adds a comment to the question
98
+ if (new_question := question.split("_comment")[0]) in d:
99
+ d[new_question].comment = answer
122
100
  return d
123
101
 
124
102
  @property
@@ -131,123 +109,6 @@ class PromptConstructor:
131
109
  question_file_keys.append(var)
132
110
  return question_file_keys
133
111
 
134
- def build_replacement_dict(self, question_data: dict):
135
- """
136
- Builds a dictionary of replacement values by combining multiple data sources.
137
- """
138
- # File references dictionary
139
- file_refs = {key: f"<see file {key}>" for key in self.scenario_file_keys}
140
-
141
- # Scenario items excluding file keys
142
- scenario_items = {
143
- k: v for k, v in self.scenario.items() if k not in self.scenario_file_keys
144
- }
145
-
146
- # Question settings with defaults
147
- question_settings = {
148
- "use_code": getattr(self.question, "_use_code", True),
149
- "include_comment": getattr(self.question, "_include_comment", False),
150
- }
151
-
152
- # Combine all dictionaries using dict.update() for clarity
153
- replacement_dict = {}
154
- for d in [
155
- file_refs,
156
- question_data,
157
- scenario_items,
158
- self.prior_answers_dict(),
159
- {"agent": self.agent},
160
- question_settings,
161
- ]:
162
- replacement_dict.update(d)
163
-
164
- return replacement_dict
165
-
166
- def _get_question_options(self, question_data):
167
- question_options_entry = question_data.get("question_options", None)
168
- question_options = question_options_entry
169
-
170
- placeholder = ["<< Option 1 - Placholder >>", "<< Option 2 - Placholder >>"]
171
-
172
- if isinstance(question_options_entry, str):
173
- env = Environment()
174
- parsed_content = env.parse(question_options_entry)
175
- question_option_key = list(meta.find_undeclared_variables(parsed_content))[
176
- 0
177
- ]
178
- if isinstance(self.scenario.get(question_option_key), list):
179
- question_options = self.scenario.get(question_option_key)
180
-
181
- # might be getting it from the prior answers
182
- if self.prior_answers_dict().get(question_option_key) is not None:
183
- prior_question = self.prior_answers_dict().get(question_option_key)
184
- if hasattr(prior_question, "answer"):
185
- if isinstance(prior_question.answer, list):
186
- question_options = prior_question.answer
187
- else:
188
- question_options = placeholder
189
- else:
190
- question_options = placeholder
191
-
192
- return question_options
193
-
194
- def build_question_instructions_prompt(self):
195
- """Buils the question instructions prompt."""
196
-
197
- question_prompt = Prompt(self.question.get_instructions(model=self.model.model))
198
-
199
- # Get the data for the question - this is a dictionary of the question data
200
- # e.g., {'question_text': 'Do you like school?', 'question_name': 'q0', 'question_options': ['yes', 'no']}
201
- question_data = self.question.data.copy()
202
-
203
- if "question_options" in question_data:
204
- question_options = self._get_question_options(question_data)
205
- question_data["question_options"] = question_options
206
-
207
- # check to see if the question_options is actually a string
208
- # This is used when the user is using the question_options as a variable from a scenario
209
- # if "question_options" in question_data:
210
- replacement_dict = self.build_replacement_dict(question_data)
211
- rendered_instructions = question_prompt.render(replacement_dict)
212
-
213
- # is there anything left to render?
214
- undefined_template_variables = (
215
- rendered_instructions.undefined_template_variables({})
216
- )
217
-
218
- # Check if it's the name of a question in the survey
219
- for question_name in self.survey.question_names:
220
- if question_name in undefined_template_variables:
221
- print(
222
- "Question name found in undefined_template_variables: ",
223
- question_name,
224
- )
225
-
226
- if undefined_template_variables:
227
- msg = f"Question instructions still has variables: {undefined_template_variables}."
228
- import warnings
229
-
230
- warnings.warn(msg)
231
- # raise QuestionScenarioRenderError(
232
- # f"Question instructions still has variables: {undefined_template_variables}."
233
- # )
234
-
235
- # Check if question has instructions - these are instructions in a Survey that can apply to multiple follow-on questions
236
- relevant_instructions = self.survey.relevant_instructions(
237
- self.question.question_name
238
- )
239
-
240
- if relevant_instructions != []:
241
- # preamble_text = Prompt(
242
- # text="You were given the following instructions: "
243
- # )
244
- preamble_text = Prompt(text="")
245
- for instruction in relevant_instructions:
246
- preamble_text += instruction.text
247
- rendered_instructions = preamble_text + rendered_instructions
248
-
249
- return rendered_instructions
250
-
251
112
  @property
252
113
  def question_instructions_prompt(self) -> Prompt:
253
114
  """
@@ -257,11 +118,107 @@ class PromptConstructor:
257
118
  Prompt(text=\"""...
258
119
  ...
259
120
  """
121
+ # The user might have passed a custom prompt, which would be stored in _question_instructions_prompt
260
122
  if not hasattr(self, "_question_instructions_prompt"):
261
- self._question_instructions_prompt = (
262
- self.build_question_instructions_prompt()
123
+ # Gets the instructions for the question - this is how the question should be answered
124
+ question_prompt = self.question.get_instructions(model=self.model.model)
125
+
126
+ # Get the data for the question - this is a dictionary of the question data
127
+ # e.g., {'question_text': 'Do you like school?', 'question_name': 'q0', 'question_options': ['yes', 'no']}
128
+ question_data = self.question.data.copy()
129
+
130
+ # check to see if the question_options is actually a string
131
+ # This is used when the user is using the question_options as a variable from a scenario
132
+ # if "question_options" in question_data:
133
+ if isinstance(self.question.data.get("question_options", None), str):
134
+ env = Environment()
135
+ parsed_content = env.parse(self.question.data["question_options"])
136
+ question_option_key = list(
137
+ meta.find_undeclared_variables(parsed_content)
138
+ )[0]
139
+
140
+ # look to see if the question_option_key is in the scenario
141
+ if isinstance(
142
+ question_options := self.scenario.get(question_option_key), list
143
+ ):
144
+ question_data["question_options"] = question_options
145
+ self.question.question_options = question_options
146
+
147
+ # might be getting it from the prior answers
148
+ if self.prior_answers_dict().get(question_option_key) is not None:
149
+ prior_question = self.prior_answers_dict().get(question_option_key)
150
+ if hasattr(prior_question, "answer"):
151
+ if isinstance(prior_question.answer, list):
152
+ question_data["question_options"] = prior_question.answer
153
+ self.question.question_options = prior_question.answer
154
+ else:
155
+ placeholder_options = [
156
+ "N/A",
157
+ "Will be populated by prior answer",
158
+ "These are placeholder options",
159
+ ]
160
+ question_data["question_options"] = placeholder_options
161
+ self.question.question_options = placeholder_options
162
+
163
+ replacement_dict = (
164
+ {key: f"<see file {key}>" for key in self.scenario_file_keys}
165
+ | question_data
166
+ | {
167
+ k: v
168
+ for k, v in self.scenario.items()
169
+ if k not in self.scenario_file_keys
170
+ } # don't include images in the replacement dict
171
+ | self.prior_answers_dict()
172
+ | {"agent": self.agent}
173
+ | {
174
+ "use_code": getattr(self.question, "_use_code", True),
175
+ "include_comment": getattr(
176
+ self.question, "_include_comment", False
177
+ ),
178
+ }
179
+ )
180
+
181
+ rendered_instructions = question_prompt.render(replacement_dict)
182
+
183
+ # is there anything left to render?
184
+ undefined_template_variables = (
185
+ rendered_instructions.undefined_template_variables({})
186
+ )
187
+
188
+ # Check if it's the name of a question in the survey
189
+ for question_name in self.survey.question_names:
190
+ if question_name in undefined_template_variables:
191
+ print(
192
+ "Question name found in undefined_template_variables: ",
193
+ question_name,
194
+ )
195
+
196
+ if undefined_template_variables:
197
+ msg = f"Question instructions still has variables: {undefined_template_variables}."
198
+ import warnings
199
+
200
+ warnings.warn(msg)
201
+ # raise QuestionScenarioRenderError(
202
+ # f"Question instructions still has variables: {undefined_template_variables}."
203
+ # )
204
+
205
+ ####################################
206
+ # Check if question has instructions - these are instructions in a Survey that can apply to multiple follow-on questions
207
+ ####################################
208
+ relevant_instructions = self.survey.relevant_instructions(
209
+ self.question.question_name
263
210
  )
264
211
 
212
+ if relevant_instructions != []:
213
+ # preamble_text = Prompt(
214
+ # text="You were given the following instructions: "
215
+ # )
216
+ preamble_text = Prompt(text="")
217
+ for instruction in relevant_instructions:
218
+ preamble_text += instruction.text
219
+ rendered_instructions = preamble_text + rendered_instructions
220
+
221
+ self._question_instructions_prompt = rendered_instructions
265
222
  return self._question_instructions_prompt
266
223
 
267
224
  @property
@@ -326,7 +283,7 @@ class PromptConstructor:
326
283
  prompts = self.prompt_plan.get_prompts(
327
284
  agent_instructions=self.agent_instructions_prompt,
328
285
  agent_persona=self.agent_persona_prompt,
329
- question_instructions=Prompt(self.question_instructions_prompt),
286
+ question_instructions=self.question_instructions_prompt,
330
287
  prior_question_memory=self.prior_question_memory_prompt,
331
288
  )
332
289
  if self.question_file_keys:
@@ -4,20 +4,6 @@ from typing import Dict
4
4
  from edsl.exceptions.agents import AgentNameError, AgentTraitKeyError
5
5
 
6
6
 
7
- def convert_agent_name(x):
8
- # potentially a numpy int64
9
- import numpy as np
10
-
11
- if isinstance(x, np.int64):
12
- return int(x)
13
- elif x is None:
14
- return None
15
- elif isinstance(x, int):
16
- return x
17
- else:
18
- return str(x)
19
-
20
-
21
7
  class NameDescriptor:
22
8
  """ABC for something."""
23
9
 
@@ -27,7 +13,7 @@ class NameDescriptor:
27
13
 
28
14
  def __set__(self, instance, name: str) -> None:
29
15
  """Set the value of the attribute."""
30
- instance.__dict__[self.name] = convert_agent_name(name)
16
+ instance.__dict__[self.name] = name
31
17
 
32
18
  def __set_name__(self, owner, name: str) -> None:
33
19
  """Set the name of the attribute."""
@@ -48,8 +34,9 @@ class TraitsDescriptor:
48
34
  for key, value in traits_dict.items():
49
35
  if key == "name":
50
36
  raise AgentNameError(
51
- "Trait keys cannot be 'name'. Instead, use the 'name' attribute directly e.g.,\n"
52
- 'Agent(name="my_agent", traits={"trait1": "value1", "trait2": "value2"})'
37
+ """Trait keys cannot be 'name'. Instead, use the 'name' attribute directly e.g.,
38
+ Agent(name="my_agent", traits={"trait1": "value1", "trait2": "value2"})
39
+ """
53
40
  )
54
41
 
55
42
  if not is_valid_variable_name(key):