edsl 0.1.37__py3-none-any.whl → 0.1.37.dev2__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/__version__.py +1 -1
- edsl/agents/Agent.py +35 -86
- edsl/agents/AgentList.py +0 -5
- edsl/agents/InvigilatorBase.py +23 -2
- edsl/agents/PromptConstructor.py +106 -147
- edsl/agents/descriptors.py +4 -17
- edsl/conjure/AgentConstructionMixin.py +3 -11
- edsl/conversation/Conversation.py +14 -66
- edsl/coop/coop.py +14 -148
- edsl/data/Cache.py +1 -1
- edsl/exceptions/__init__.py +3 -7
- edsl/exceptions/agents.py +19 -17
- edsl/exceptions/results.py +8 -11
- edsl/exceptions/surveys.py +10 -13
- edsl/inference_services/AwsBedrock.py +2 -7
- edsl/inference_services/InferenceServicesCollection.py +9 -32
- edsl/jobs/Jobs.py +71 -306
- edsl/jobs/interviews/InterviewExceptionEntry.py +1 -5
- edsl/jobs/tasks/TaskHistory.py +0 -1
- edsl/language_models/LanguageModel.py +59 -47
- edsl/language_models/__init__.py +0 -1
- edsl/prompts/Prompt.py +4 -8
- edsl/questions/QuestionBase.py +13 -53
- edsl/questions/QuestionBasePromptsMixin.py +33 -1
- edsl/questions/QuestionFunctional.py +2 -2
- edsl/questions/descriptors.py +28 -23
- edsl/results/DatasetExportMixin.py +1 -25
- edsl/results/Result.py +1 -16
- edsl/results/Results.py +120 -31
- edsl/results/ResultsDBMixin.py +1 -1
- edsl/results/Selector.py +1 -18
- edsl/scenarios/Scenario.py +12 -48
- edsl/scenarios/ScenarioHtmlMixin.py +2 -7
- edsl/scenarios/ScenarioList.py +1 -12
- edsl/surveys/Rule.py +4 -10
- edsl/surveys/Survey.py +77 -100
- edsl/utilities/utilities.py +0 -18
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/METADATA +1 -1
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/RECORD +41 -45
- edsl/conversation/chips.py +0 -95
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/scenarios.py +0 -22
- edsl/language_models/KeyLookup.py +0 -30
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dist-info → edsl-0.1.37.dev2.dist-info}/WHEEL +0 -0
edsl/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.37"
|
1
|
+
__version__ = "0.1.37.dev2"
|
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
|
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
|
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
|
-
|
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[
|
364
|
-
model: Optional[
|
307
|
+
scenario: Optional[Scenario] = None,
|
308
|
+
model: Optional[LanguageModel] = None,
|
365
309
|
debug: bool = False,
|
366
|
-
memory_plan: Optional[
|
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
|
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
|
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
|
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
|
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))
|
edsl/agents/InvigilatorBase.py
CHANGED
@@ -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
|
-
|
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]
|
edsl/agents/PromptConstructor.py
CHANGED
@@ -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
|
112
|
-
if question in
|
113
|
-
d[question].answer =
|
93
|
+
for question, answer in self.current_answers.items():
|
94
|
+
if question in d:
|
95
|
+
d[question].answer = answer
|
114
96
|
else:
|
115
|
-
|
116
|
-
|
117
|
-
|
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,109 @@ 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
|
-
|
262
|
-
|
123
|
+
# Gets the instructions for the question - this is how the question should be answered
|
124
|
+
question_prompt = Prompt(
|
125
|
+
self.question.get_instructions(model=self.model.model)
|
263
126
|
)
|
264
127
|
|
128
|
+
# Get the data for the question - this is a dictionary of the question data
|
129
|
+
# e.g., {'question_text': 'Do you like school?', 'question_name': 'q0', 'question_options': ['yes', 'no']}
|
130
|
+
question_data = self.question.data.copy()
|
131
|
+
|
132
|
+
# check to see if the question_options is actually a string
|
133
|
+
# This is used when the user is using the question_options as a variable from a scenario
|
134
|
+
# if "question_options" in question_data:
|
135
|
+
if isinstance(self.question.data.get("question_options", None), str):
|
136
|
+
env = Environment()
|
137
|
+
parsed_content = env.parse(self.question.data["question_options"])
|
138
|
+
question_option_key = list(
|
139
|
+
meta.find_undeclared_variables(parsed_content)
|
140
|
+
)[0]
|
141
|
+
|
142
|
+
# look to see if the question_option_key is in the scenario
|
143
|
+
if isinstance(
|
144
|
+
question_options := self.scenario.get(question_option_key), list
|
145
|
+
):
|
146
|
+
question_data["question_options"] = question_options
|
147
|
+
self.question.question_options = question_options
|
148
|
+
|
149
|
+
# might be getting it from the prior answers
|
150
|
+
if self.prior_answers_dict().get(question_option_key) is not None:
|
151
|
+
prior_question = self.prior_answers_dict().get(question_option_key)
|
152
|
+
if hasattr(prior_question, "answer"):
|
153
|
+
if isinstance(prior_question.answer, list):
|
154
|
+
question_data["question_options"] = prior_question.answer
|
155
|
+
self.question.question_options = prior_question.answer
|
156
|
+
else:
|
157
|
+
placeholder_options = [
|
158
|
+
"N/A",
|
159
|
+
"Will be populated by prior answer",
|
160
|
+
"These are placeholder options",
|
161
|
+
]
|
162
|
+
question_data["question_options"] = placeholder_options
|
163
|
+
self.question.question_options = placeholder_options
|
164
|
+
|
165
|
+
replacement_dict = (
|
166
|
+
{key: f"<see file {key}>" for key in self.scenario_file_keys}
|
167
|
+
| question_data
|
168
|
+
| {
|
169
|
+
k: v
|
170
|
+
for k, v in self.scenario.items()
|
171
|
+
if k not in self.scenario_file_keys
|
172
|
+
} # don't include images in the replacement dict
|
173
|
+
| self.prior_answers_dict()
|
174
|
+
| {"agent": self.agent}
|
175
|
+
| {
|
176
|
+
"use_code": getattr(self.question, "_use_code", True),
|
177
|
+
"include_comment": getattr(
|
178
|
+
self.question, "_include_comment", False
|
179
|
+
),
|
180
|
+
}
|
181
|
+
)
|
182
|
+
|
183
|
+
rendered_instructions = question_prompt.render(replacement_dict)
|
184
|
+
|
185
|
+
# is there anything left to render?
|
186
|
+
undefined_template_variables = (
|
187
|
+
rendered_instructions.undefined_template_variables({})
|
188
|
+
)
|
189
|
+
|
190
|
+
# Check if it's the name of a question in the survey
|
191
|
+
for question_name in self.survey.question_names:
|
192
|
+
if question_name in undefined_template_variables:
|
193
|
+
print(
|
194
|
+
"Question name found in undefined_template_variables: ",
|
195
|
+
question_name,
|
196
|
+
)
|
197
|
+
|
198
|
+
if undefined_template_variables:
|
199
|
+
msg = f"Question instructions still has variables: {undefined_template_variables}."
|
200
|
+
import warnings
|
201
|
+
|
202
|
+
warnings.warn(msg)
|
203
|
+
# raise QuestionScenarioRenderError(
|
204
|
+
# f"Question instructions still has variables: {undefined_template_variables}."
|
205
|
+
# )
|
206
|
+
|
207
|
+
####################################
|
208
|
+
# Check if question has instructions - these are instructions in a Survey that can apply to multiple follow-on questions
|
209
|
+
####################################
|
210
|
+
relevant_instructions = self.survey.relevant_instructions(
|
211
|
+
self.question.question_name
|
212
|
+
)
|
213
|
+
|
214
|
+
if relevant_instructions != []:
|
215
|
+
# preamble_text = Prompt(
|
216
|
+
# text="You were given the following instructions: "
|
217
|
+
# )
|
218
|
+
preamble_text = Prompt(text="")
|
219
|
+
for instruction in relevant_instructions:
|
220
|
+
preamble_text += instruction.text
|
221
|
+
rendered_instructions = preamble_text + rendered_instructions
|
222
|
+
|
223
|
+
self._question_instructions_prompt = rendered_instructions
|
265
224
|
return self._question_instructions_prompt
|
266
225
|
|
267
226
|
@property
|
edsl/agents/descriptors.py
CHANGED
@@ -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] =
|
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
|
52
|
-
|
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):
|