edsl 0.1.29__py3-none-any.whl → 0.1.29.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.
- edsl/Base.py +18 -18
- edsl/__init__.py +24 -24
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +41 -77
- edsl/agents/AgentList.py +6 -35
- edsl/agents/Invigilator.py +1 -19
- edsl/agents/InvigilatorBase.py +10 -15
- edsl/agents/PromptConstructionMixin.py +100 -342
- edsl/agents/descriptors.py +1 -2
- edsl/config.py +1 -2
- edsl/conjure/InputData.py +8 -39
- edsl/coop/coop.py +150 -187
- edsl/coop/utils.py +75 -43
- edsl/data/Cache.py +5 -19
- edsl/data/SQLiteDict.py +3 -11
- edsl/jobs/Answers.py +1 -15
- edsl/jobs/Jobs.py +46 -90
- edsl/jobs/buckets/ModelBuckets.py +2 -4
- edsl/jobs/buckets/TokenBucket.py +2 -1
- edsl/jobs/interviews/Interview.py +9 -3
- edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +10 -15
- edsl/jobs/runners/JobsRunnerAsyncio.py +25 -21
- edsl/jobs/tasks/TaskHistory.py +3 -4
- edsl/language_models/LanguageModel.py +11 -5
- edsl/language_models/ModelList.py +3 -3
- edsl/language_models/repair.py +7 -8
- edsl/notebooks/Notebook.py +3 -40
- edsl/prompts/Prompt.py +19 -31
- edsl/questions/QuestionBase.py +13 -38
- edsl/questions/QuestionBudget.py +6 -5
- edsl/questions/QuestionCheckBox.py +3 -7
- edsl/questions/QuestionExtract.py +3 -5
- edsl/questions/QuestionFreeText.py +3 -3
- edsl/questions/QuestionFunctional.py +3 -0
- edsl/questions/QuestionList.py +4 -3
- edsl/questions/QuestionMultipleChoice.py +8 -16
- edsl/questions/QuestionNumerical.py +3 -4
- edsl/questions/QuestionRank.py +3 -5
- edsl/questions/__init__.py +3 -4
- edsl/questions/descriptors.py +2 -4
- edsl/questions/question_registry.py +31 -20
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +0 -31
- edsl/results/Result.py +74 -22
- edsl/results/Results.py +47 -97
- edsl/results/ResultsDBMixin.py +3 -7
- edsl/results/ResultsExportMixin.py +537 -22
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +5 -5
- edsl/scenarios/Scenario.py +6 -5
- edsl/scenarios/ScenarioList.py +11 -34
- edsl/scenarios/ScenarioListPdfMixin.py +1 -2
- edsl/scenarios/__init__.py +0 -1
- edsl/study/ObjectEntry.py +13 -89
- edsl/study/ProofOfWork.py +2 -5
- edsl/study/SnapShot.py +8 -4
- edsl/study/Study.py +14 -21
- edsl/study/__init__.py +0 -2
- edsl/surveys/MemoryPlan.py +4 -11
- edsl/surveys/Survey.py +7 -46
- edsl/surveys/SurveyExportMixin.py +2 -4
- edsl/surveys/SurveyFlowVisualizationMixin.py +4 -6
- edsl/tools/plotting.py +2 -4
- edsl/utilities/__init__.py +21 -21
- edsl/utilities/interface.py +45 -66
- edsl/utilities/utilities.py +13 -11
- {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/METADATA +10 -11
- {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/RECORD +72 -75
- edsl-0.1.29.dev1.dist-info/entry_points.txt +3 -0
- edsl/base/Base.py +0 -289
- edsl/results/DatasetExportMixin.py +0 -493
- edsl/scenarios/FileStore.py +0 -140
- edsl/scenarios/ScenarioListExportMixin.py +0 -32
- {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/LICENSE +0 -0
- {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/WHEEL +0 -0
@@ -1,376 +1,134 @@
|
|
1
|
-
from typing import Dict, Any
|
2
|
-
from collections import UserList
|
1
|
+
from typing import Dict, Any
|
3
2
|
|
4
|
-
# from functools import reduce
|
5
3
|
from edsl.prompts.Prompt import Prompt
|
6
|
-
|
7
|
-
# from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
4
|
+
from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
8
5
|
from edsl.prompts.registry import get_classes as prompt_lookup
|
9
6
|
from edsl.exceptions import QuestionScenarioRenderError
|
10
7
|
|
11
|
-
import enum
|
12
|
-
|
13
|
-
|
14
|
-
class PromptComponent(enum.Enum):
|
15
|
-
AGENT_INSTRUCTIONS = "agent_instructions"
|
16
|
-
AGENT_PERSONA = "agent_persona"
|
17
|
-
QUESTION_INSTRUCTIONS = "question_instructions"
|
18
|
-
PRIOR_QUESTION_MEMORY = "prior_question_memory"
|
19
|
-
|
20
|
-
|
21
|
-
class PromptList(UserList):
|
22
|
-
separator = Prompt(" ")
|
23
|
-
|
24
|
-
def reduce(self):
|
25
|
-
"""Reduce the list of prompts to a single prompt.
|
26
|
-
|
27
|
-
>>> p = PromptList([Prompt("You are a happy-go lucky agent."), Prompt("You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}")])
|
28
|
-
>>> p.reduce()
|
29
|
-
Prompt(text=\"""You are a happy-go lucky agent. You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}\""")
|
30
|
-
|
31
|
-
"""
|
32
|
-
p = self[0]
|
33
|
-
for prompt in self[1:]:
|
34
|
-
if len(prompt) > 0:
|
35
|
-
p = p + self.separator + prompt
|
36
|
-
return p
|
37
|
-
|
38
|
-
|
39
|
-
class PromptPlan:
|
40
|
-
"""A plan for constructing prompts for the LLM call.
|
41
|
-
Every prompt plan has a user prompt order and a system prompt order.
|
42
|
-
It must contain each of the values in the PromptComponent enum.
|
43
|
-
|
44
|
-
|
45
|
-
>>> p = PromptPlan(user_prompt_order=(PromptComponent.AGENT_INSTRUCTIONS, PromptComponent.AGENT_PERSONA),system_prompt_order=(PromptComponent.QUESTION_INSTRUCTIONS, PromptComponent.PRIOR_QUESTION_MEMORY))
|
46
|
-
>>> p._is_valid_plan()
|
47
|
-
True
|
48
|
-
|
49
|
-
>>> p.arrange_components(agent_instructions=1, agent_persona=2, question_instructions=3, prior_question_memory=4)
|
50
|
-
{'user_prompt': ..., 'system_prompt': ...}
|
51
|
-
|
52
|
-
>>> p = PromptPlan(user_prompt_order=("agent_instructions", ), system_prompt_order=("question_instructions", "prior_question_memory"))
|
53
|
-
Traceback (most recent call last):
|
54
|
-
...
|
55
|
-
ValueError: Invalid plan: must contain each value of PromptComponent exactly once.
|
56
|
-
|
57
|
-
"""
|
58
|
-
|
59
|
-
def __init__(
|
60
|
-
self,
|
61
|
-
user_prompt_order: Optional[tuple] = None,
|
62
|
-
system_prompt_order: Optional[tuple] = None,
|
63
|
-
):
|
64
|
-
"""Initialize the PromptPlan."""
|
65
|
-
|
66
|
-
if user_prompt_order is None:
|
67
|
-
user_prompt_order = (
|
68
|
-
PromptComponent.QUESTION_INSTRUCTIONS,
|
69
|
-
PromptComponent.PRIOR_QUESTION_MEMORY,
|
70
|
-
)
|
71
|
-
if system_prompt_order is None:
|
72
|
-
system_prompt_order = (
|
73
|
-
PromptComponent.AGENT_INSTRUCTIONS,
|
74
|
-
PromptComponent.AGENT_PERSONA,
|
75
|
-
)
|
76
|
-
|
77
|
-
# very commmon way to screw this up given how python treats single strings as iterables
|
78
|
-
if isinstance(user_prompt_order, str):
|
79
|
-
user_prompt_order = (user_prompt_order,)
|
80
8
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if not isinstance(user_prompt_order, tuple):
|
85
|
-
raise TypeError(
|
86
|
-
f"Expected a tuple, but got {type(user_prompt_order).__name__}"
|
87
|
-
)
|
88
|
-
|
89
|
-
if not isinstance(system_prompt_order, tuple):
|
90
|
-
raise TypeError(
|
91
|
-
f"Expected a tuple, but got {type(system_prompt_order).__name__}"
|
92
|
-
)
|
93
|
-
|
94
|
-
self.user_prompt_order = self._convert_to_enum(user_prompt_order)
|
95
|
-
self.system_prompt_order = self._convert_to_enum(system_prompt_order)
|
96
|
-
if not self._is_valid_plan():
|
97
|
-
raise ValueError(
|
98
|
-
"Invalid plan: must contain each value of PromptComponent exactly once."
|
99
|
-
)
|
100
|
-
|
101
|
-
def _convert_to_enum(self, prompt_order: tuple):
|
102
|
-
"""Convert string names to PromptComponent enum values."""
|
103
|
-
return tuple(
|
104
|
-
PromptComponent(component) if isinstance(component, str) else component
|
105
|
-
for component in prompt_order
|
106
|
-
)
|
107
|
-
|
108
|
-
def _is_valid_plan(self):
|
109
|
-
"""Check if the plan is valid."""
|
110
|
-
combined = self.user_prompt_order + self.system_prompt_order
|
111
|
-
return set(combined) == set(PromptComponent)
|
9
|
+
class PromptConstructorMixin:
|
10
|
+
def construct_system_prompt(self) -> Prompt:
|
11
|
+
"""Construct the system prompt for the LLM call."""
|
112
12
|
|
113
|
-
|
114
|
-
|
115
|
-
# check is valid components passed
|
116
|
-
component_strings = set([pc.value for pc in PromptComponent])
|
117
|
-
if not set(kwargs.keys()) == component_strings:
|
118
|
-
raise ValueError(
|
119
|
-
f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
|
120
|
-
)
|
13
|
+
agent_instructions = self._get_agent_instructions_prompt()
|
14
|
+
persona_prompt = self._get_persona_prompt()
|
121
15
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
[kwargs[component.value] for component in self.system_prompt_order]
|
16
|
+
return (
|
17
|
+
agent_instructions
|
18
|
+
+ " " * int(len(persona_prompt.text) > 0)
|
19
|
+
+ persona_prompt
|
127
20
|
)
|
128
|
-
return {"user_prompt": user_prompt, "system_prompt": system_prompt}
|
129
|
-
|
130
|
-
def get_prompts(self, **kwargs) -> Dict[str, Prompt]:
|
131
|
-
"""Get both prompts for the LLM call."""
|
132
|
-
prompts = self.arrange_components(**kwargs)
|
133
|
-
return {
|
134
|
-
"user_prompt": prompts["user_prompt"].reduce(),
|
135
|
-
"system_prompt": prompts["system_prompt"].reduce(),
|
136
|
-
}
|
137
|
-
|
138
|
-
|
139
|
-
class PromptConstructorMixin:
|
140
|
-
"""Mixin for constructing prompts for the LLM call.
|
141
|
-
|
142
|
-
The pieces of a prompt are:
|
143
|
-
- The agent instructions - "You are answering questions as if you were a human. Do not break character."
|
144
|
-
- The persona prompt - "You are an agent with the following persona: {'age': 22, 'hair': 'brown', 'height': 5.5}"
|
145
|
-
- The question instructions - "You are being asked the following question: Do you like school? The options are 0: yes 1: no Return a valid JSON formatted like this, selecting only the number of the option: {"answer": <put answer code here>, "comment": "<put explanation here>"} Only 1 option may be selected."
|
146
|
-
- The memory prompt - "Before the question you are now answering, you already answered the following question(s): Question: Do you like school? Answer: Prior answer"
|
147
21
|
|
148
|
-
|
149
|
-
|
22
|
+
def _get_persona_prompt(self) -> Prompt:
|
23
|
+
"""Get the persona prompt.
|
150
24
|
|
151
|
-
|
25
|
+
The is the description of the agent to the LLM.
|
152
26
|
|
153
|
-
|
154
|
-
|
27
|
+
The agent_persona is constructed when the Agent is created.
|
28
|
+
If the agent is passed a template for "agent_trait_presentation_template" that is used to construct the persona.
|
29
|
+
If it does not exist, the persona is looked up in the prompt registry
|
155
30
|
"""
|
156
|
-
|
157
|
-
>>> i = InvigilatorBase.example()
|
158
|
-
>>> i.agent_instructions_prompt
|
159
|
-
Prompt(text=\"""You are answering questions as if you were a human. Do not break character.\""")
|
160
|
-
"""
|
161
|
-
if not hasattr(self, "_agent_instructions_prompt"):
|
31
|
+
if not hasattr(self.agent, "agent_persona"):
|
162
32
|
applicable_prompts = prompt_lookup(
|
163
|
-
component_type="
|
33
|
+
component_type="agent_persona",
|
164
34
|
model=self.model.model,
|
165
35
|
)
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
36
|
+
persona_prompt_template = applicable_prompts[0]()
|
37
|
+
else:
|
38
|
+
persona_prompt_template = self.agent.agent_persona
|
39
|
+
|
40
|
+
# TODO: This multiple passing of agent traits - not sure if it is necessary. Not harmful.
|
41
|
+
if undefined := persona_prompt_template.undefined_template_variables(
|
42
|
+
self.agent.traits
|
43
|
+
| {"traits": self.agent.traits}
|
44
|
+
| {"codebook": self.agent.codebook}
|
45
|
+
| {"traits": self.agent.traits}
|
46
|
+
):
|
47
|
+
raise QuestionScenarioRenderError(
|
48
|
+
f"Agent persona still has variables that were not rendered: {undefined}"
|
170
49
|
)
|
171
|
-
return self._agent_instructions_prompt
|
172
|
-
|
173
|
-
@property
|
174
|
-
def agent_persona_prompt(self) -> Prompt:
|
175
|
-
"""
|
176
|
-
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
177
|
-
>>> i = InvigilatorBase.example()
|
178
|
-
>>> i.agent_persona_prompt
|
179
|
-
Prompt(text=\"""You are an agent with the following persona:
|
180
|
-
{'age': 22, 'hair': 'brown', 'height': 5.5}\""")
|
181
|
-
|
182
|
-
"""
|
183
|
-
if not hasattr(self, "_agent_persona_prompt"):
|
184
|
-
if not hasattr(self.agent, "agent_persona"):
|
185
|
-
applicable_prompts = prompt_lookup(
|
186
|
-
component_type="agent_persona",
|
187
|
-
model=self.model.model,
|
188
|
-
)
|
189
|
-
persona_prompt_template = applicable_prompts[0]()
|
190
|
-
else:
|
191
|
-
persona_prompt_template = self.agent.agent_persona
|
192
|
-
|
193
|
-
# TODO: This multiple passing of agent traits - not sure if it is necessary. Not harmful.
|
194
|
-
if undefined := persona_prompt_template.undefined_template_variables(
|
195
|
-
self.agent.traits
|
196
|
-
| {"traits": self.agent.traits}
|
197
|
-
| {"codebook": self.agent.codebook}
|
198
|
-
| {"traits": self.agent.traits}
|
199
|
-
):
|
200
|
-
raise QuestionScenarioRenderError(
|
201
|
-
f"Agent persona still has variables that were not rendered: {undefined}"
|
202
|
-
)
|
203
|
-
|
204
|
-
persona_prompt = persona_prompt_template.render(
|
205
|
-
self.agent.traits | {"traits": self.agent.traits},
|
206
|
-
codebook=self.agent.codebook,
|
207
|
-
traits=self.agent.traits,
|
208
|
-
)
|
209
|
-
if persona_prompt.has_variables:
|
210
|
-
raise QuestionScenarioRenderError(
|
211
|
-
"Agent persona still has variables that were not rendered."
|
212
|
-
)
|
213
|
-
self._agent_persona_prompt = persona_prompt
|
214
|
-
|
215
|
-
return self._agent_persona_prompt
|
216
|
-
|
217
|
-
@property
|
218
|
-
def question_instructions_prompt(self) -> Prompt:
|
219
|
-
"""
|
220
|
-
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
221
|
-
>>> i = InvigilatorBase.example()
|
222
|
-
>>> i.question_instructions_prompt
|
223
|
-
Prompt(text=\"""You are being asked the following question: Do you like school?
|
224
|
-
The options are
|
225
|
-
<BLANKLINE>
|
226
|
-
0: yes
|
227
|
-
<BLANKLINE>
|
228
|
-
1: no
|
229
|
-
<BLANKLINE>
|
230
|
-
Return a valid JSON formatted like this, selecting only the number of the option:
|
231
|
-
{"answer": <put answer code here>, "comment": "<put explanation here>"}
|
232
|
-
Only 1 option may be selected.\""")
|
233
50
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
Traceback (most recent call last):
|
240
|
-
...
|
241
|
-
edsl.exceptions.questions.QuestionScenarioRenderError: Question instructions still has variables: ['X'].
|
242
|
-
|
243
|
-
|
244
|
-
>>> from edsl import QuestionFreeText
|
245
|
-
>>> q = QuestionFreeText(question_text = "You were asked the question '{{ q0.question_text }}'. What is your favorite color?", question_name = "q_color")
|
246
|
-
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
247
|
-
>>> i = InvigilatorBase.example(question = q)
|
248
|
-
>>> i.question_instructions_prompt
|
249
|
-
Prompt(text=\"""You are being asked the following question: You were asked the question 'Do you like school?'. What is your favorite color?
|
250
|
-
Return a valid JSON formatted like this:
|
251
|
-
{"answer": "<put free text answer here>"}\""")
|
252
|
-
|
253
|
-
>>> from edsl import QuestionFreeText
|
254
|
-
>>> q = QuestionFreeText(question_text = "You stated '{{ q0.answer }}'. What is your favorite color?", question_name = "q_color")
|
255
|
-
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
256
|
-
>>> i = InvigilatorBase.example(question = q)
|
257
|
-
>>> i.current_answers = {"q0": "I like school"}
|
258
|
-
>>> i.question_instructions_prompt
|
259
|
-
Prompt(text=\"""You are being asked the following question: You stated 'I like school'. What is your favorite color?
|
260
|
-
Return a valid JSON formatted like this:
|
261
|
-
{"answer": "<put free text answer here>"}\""")
|
262
|
-
|
263
|
-
|
264
|
-
"""
|
265
|
-
if not hasattr(self, "_question_instructions_prompt"):
|
266
|
-
question_prompt = self.question.get_instructions(model=self.model.model)
|
267
|
-
|
268
|
-
# TODO: Try to populate the answers in the question object if they are available
|
269
|
-
d = self.survey.question_names_to_questions()
|
270
|
-
for question, answer in self.current_answers.items():
|
271
|
-
if question in d:
|
272
|
-
d[question].answer = answer
|
273
|
-
else:
|
274
|
-
# adds a comment to the question
|
275
|
-
if (new_question := question.split("_comment")[0]) in d:
|
276
|
-
d[new_question].comment = answer
|
51
|
+
persona_prompt = persona_prompt_template.render(
|
52
|
+
self.agent.traits | {"traits": self.agent.traits},
|
53
|
+
codebook=self.agent.codebook,
|
54
|
+
traits=self.agent.traits,
|
55
|
+
)
|
277
56
|
|
278
|
-
|
279
|
-
|
57
|
+
if persona_prompt.has_variables:
|
58
|
+
raise QuestionScenarioRenderError(
|
59
|
+
"Agent persona still has variables that were not rendered."
|
280
60
|
)
|
61
|
+
return persona_prompt
|
281
62
|
|
282
|
-
|
283
|
-
|
63
|
+
def _get_agent_instructions_prompt(self) -> Prompt:
|
64
|
+
"""Get the agent instructions prompt."""
|
65
|
+
applicable_prompts = prompt_lookup(
|
66
|
+
component_type="agent_instructions",
|
67
|
+
model=self.model.model,
|
68
|
+
)
|
69
|
+
if len(applicable_prompts) == 0:
|
70
|
+
raise Exception("No applicable prompts found")
|
71
|
+
return applicable_prompts[0](text=self.agent.instruction)
|
72
|
+
|
73
|
+
def _get_question_instructions(self) -> Prompt:
|
74
|
+
"""Get the instructions for the question."""
|
75
|
+
# applicable_prompts = prompt_lookup(
|
76
|
+
# component_type="question_instructions",
|
77
|
+
# question_type=self.question.question_type,
|
78
|
+
# model=self.model.model,
|
79
|
+
# )
|
80
|
+
## Get the question instructions and renders with the scenario & question.data
|
81
|
+
# question_prompt = applicable_prompts[0]()
|
82
|
+
question_prompt = self.question.get_instructions(model=self.model.model)
|
83
|
+
|
84
|
+
undefined_template_variables = question_prompt.undefined_template_variables(
|
85
|
+
self.question.data | self.scenario
|
86
|
+
)
|
87
|
+
if undefined_template_variables:
|
88
|
+
print(undefined_template_variables)
|
89
|
+
raise QuestionScenarioRenderError(
|
90
|
+
"Question instructions still has variables."
|
284
91
|
)
|
285
92
|
|
286
|
-
|
287
|
-
for question_name in self.survey.question_names:
|
288
|
-
if question_name in undefined_template_variables:
|
289
|
-
print(
|
290
|
-
"Question name found in undefined_template_variables: ",
|
291
|
-
question_name,
|
292
|
-
)
|
293
|
-
|
294
|
-
if undefined_template_variables:
|
295
|
-
print(undefined_template_variables)
|
296
|
-
raise QuestionScenarioRenderError(
|
297
|
-
f"Question instructions still has variables: {undefined_template_variables}."
|
298
|
-
)
|
299
|
-
|
300
|
-
self._question_instructions_prompt = rendered_instructions
|
301
|
-
return self._question_instructions_prompt
|
302
|
-
|
303
|
-
@property
|
304
|
-
def prior_question_memory_prompt(self) -> Prompt:
|
305
|
-
if not hasattr(self, "_prior_question_memory_prompt"):
|
306
|
-
from edsl.prompts.Prompt import Prompt
|
307
|
-
|
308
|
-
memory_prompt = Prompt(text="")
|
309
|
-
if self.memory_plan is not None:
|
310
|
-
memory_prompt += self.create_memory_prompt(
|
311
|
-
self.question.question_name
|
312
|
-
).render(self.scenario)
|
313
|
-
self._prior_question_memory_prompt = memory_prompt
|
314
|
-
return self._prior_question_memory_prompt
|
315
|
-
|
316
|
-
def construct_system_prompt(self) -> Prompt:
|
317
|
-
"""Construct the system prompt for the LLM call."""
|
318
|
-
import warnings
|
319
|
-
|
320
|
-
warnings.warn(
|
321
|
-
"This method is deprecated. Use get_prompts instead.", DeprecationWarning
|
322
|
-
)
|
323
|
-
return self.get_prompts()["system_prompt"]
|
93
|
+
return question_prompt.render(self.question.data | self.scenario)
|
324
94
|
|
325
95
|
def construct_user_prompt(self) -> Prompt:
|
326
96
|
"""Construct the user prompt for the LLM call."""
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
return
|
97
|
+
user_prompt = self._get_question_instructions()
|
98
|
+
if self.memory_plan is not None:
|
99
|
+
user_prompt += self.create_memory_prompt(
|
100
|
+
self.question.question_name
|
101
|
+
).render(self.scenario)
|
102
|
+
return user_prompt
|
333
103
|
|
334
104
|
def get_prompts(self) -> Dict[str, Prompt]:
|
335
|
-
"""Get both prompts for the LLM call.
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
{'user_prompt': ..., 'system_prompt': ...}
|
343
|
-
>>> scenario = i._get_scenario_with_image()
|
344
|
-
>>> scenario.has_image
|
345
|
-
True
|
346
|
-
>>> q = QuestionFreeText(question_text="How are you today?", question_name="q0")
|
347
|
-
>>> i = InvigilatorBase.example(question = q, scenario = scenario)
|
348
|
-
>>> i.get_prompts()
|
349
|
-
{'user_prompt': ..., 'system_prompt': ..., 'encoded_image': ...'}
|
350
|
-
"""
|
351
|
-
prompts = self.prompt_plan.get_prompts(
|
352
|
-
agent_instructions=self.agent_instructions_prompt,
|
353
|
-
agent_persona=self.agent_persona_prompt,
|
354
|
-
question_instructions=self.question_instructions_prompt,
|
355
|
-
prior_question_memory=self.prior_question_memory_prompt,
|
356
|
-
)
|
357
|
-
|
105
|
+
"""Get both prompts for the LLM call."""
|
106
|
+
system_prompt = self.construct_system_prompt()
|
107
|
+
user_prompt = self.construct_user_prompt()
|
108
|
+
prompts = {
|
109
|
+
"user_prompt": user_prompt,
|
110
|
+
"system_prompt": system_prompt,
|
111
|
+
}
|
358
112
|
if hasattr(self.scenario, "has_image") and self.scenario.has_image:
|
359
113
|
prompts["encoded_image"] = self.scenario["encoded_image"]
|
360
114
|
return prompts
|
361
115
|
|
362
|
-
def _get_scenario_with_image(self) -> Dict[str, Any]:
|
363
|
-
"""This is a helper function to get a scenario with an image, for testing purposes."""
|
364
|
-
from edsl import Scenario
|
365
|
-
|
366
|
-
try:
|
367
|
-
scenario = Scenario.from_image("../../static/logo.png")
|
368
|
-
except FileNotFoundError:
|
369
|
-
scenario = Scenario.from_image("static/logo.png")
|
370
|
-
return scenario
|
371
|
-
|
372
116
|
|
373
117
|
if __name__ == "__main__":
|
374
|
-
import
|
375
|
-
|
376
|
-
|
118
|
+
from edsl import Model
|
119
|
+
from edsl import Agent
|
120
|
+
|
121
|
+
a = Agent(
|
122
|
+
instruction="You are a happy-go lucky agent.",
|
123
|
+
traits={"feeling": "happy", "age": "Young at heart"},
|
124
|
+
codebook={"feeling": "Feelings right now", "age": "Age in years"},
|
125
|
+
trait_presentation_template="",
|
126
|
+
)
|
127
|
+
p = PromptConstructorMixin()
|
128
|
+
p.model = Model(Model.available()[0])
|
129
|
+
p.agent = a
|
130
|
+
instructions = p._get_agent_instructions_prompt()
|
131
|
+
repr(instructions)
|
132
|
+
|
133
|
+
persona = p._get_persona_prompt()
|
134
|
+
repr(persona)
|
edsl/agents/descriptors.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"""This module contains the descriptors used to set the attributes of the Agent class."""
|
2
2
|
|
3
3
|
from typing import Dict
|
4
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
4
5
|
from edsl.exceptions.agents import AgentNameError, AgentTraitKeyError
|
5
6
|
|
6
7
|
|
@@ -29,8 +30,6 @@ class TraitsDescriptor:
|
|
29
30
|
|
30
31
|
def __set__(self, instance, traits_dict: Dict[str, str]) -> None:
|
31
32
|
"""Set the value of the attribute."""
|
32
|
-
from edsl.utilities.utilities import is_valid_variable_name
|
33
|
-
|
34
33
|
for key, value in traits_dict.items():
|
35
34
|
if key == "name":
|
36
35
|
raise AgentNameError(
|
edsl/config.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
"""This module provides a Config class that loads environment variables from a .env file and sets them as class attributes."""
|
2
2
|
|
3
3
|
import os
|
4
|
+
from dotenv import load_dotenv, find_dotenv
|
4
5
|
from edsl.exceptions import (
|
5
6
|
InvalidEnvironmentVariableError,
|
6
7
|
MissingEnvironmentVariableError,
|
7
8
|
)
|
8
|
-
from dotenv import load_dotenv, find_dotenv
|
9
9
|
|
10
10
|
# valid values for EDSL_RUN_MODE
|
11
11
|
EDSL_RUN_MODES = ["development", "development-testrun", "production"]
|
@@ -96,7 +96,6 @@ class Config:
|
|
96
96
|
Loads the .env
|
97
97
|
- Overrides existing env vars unless EDSL_RUN_MODE=="development-testrun"
|
98
98
|
"""
|
99
|
-
|
100
99
|
override = True
|
101
100
|
if self.EDSL_RUN_MODE == "development-testrun":
|
102
101
|
override = False
|
edsl/conjure/InputData.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import
|
1
|
+
import functools
|
2
|
+
|
2
3
|
from abc import ABC, abstractmethod
|
3
4
|
from typing import Dict, Callable, Optional, List, Generator, Tuple, Union
|
4
5
|
from collections import namedtuple
|
@@ -51,7 +52,6 @@ class InputDataABC(
|
|
51
52
|
config: Optional[dict] = None,
|
52
53
|
naming_function: Optional[Callable] = sanitize_string,
|
53
54
|
raw_data: Optional[List] = None,
|
54
|
-
binary: Optional[str] = None,
|
55
55
|
question_names: Optional[List[str]] = None,
|
56
56
|
question_texts: Optional[List[str]] = None,
|
57
57
|
answer_codebook: Optional[Dict] = None,
|
@@ -83,15 +83,6 @@ class InputDataABC(
|
|
83
83
|
self.config = config
|
84
84
|
self.naming_function = naming_function
|
85
85
|
|
86
|
-
if binary is not None:
|
87
|
-
self.binary = binary
|
88
|
-
else:
|
89
|
-
try:
|
90
|
-
with open(self.datafile_name, "rb") as file:
|
91
|
-
self.binary = base64.b64encode(file.read()).decode()
|
92
|
-
except FileNotFoundError:
|
93
|
-
self.binary = None
|
94
|
-
|
95
86
|
def default_repair_func(x):
|
96
87
|
return (
|
97
88
|
x.replace("#", "_num")
|
@@ -127,14 +118,6 @@ class InputDataABC(
|
|
127
118
|
if order_options:
|
128
119
|
self.order_options()
|
129
120
|
|
130
|
-
@property
|
131
|
-
def download_link(self):
|
132
|
-
from IPython.display import HTML
|
133
|
-
|
134
|
-
actual_file_name = self.datafile_name.split("/")[-1]
|
135
|
-
download_link = f'<a href="data:text/plain;base64,{self.binary}" download="{actual_file_name}">Download {self.datafile_name}</a>'
|
136
|
-
return HTML(download_link)
|
137
|
-
|
138
121
|
@abstractmethod
|
139
122
|
def get_question_texts(self) -> List[str]:
|
140
123
|
"""Get the text of the questions
|
@@ -168,9 +151,7 @@ class InputDataABC(
|
|
168
151
|
"""
|
169
152
|
raise NotImplementedError
|
170
153
|
|
171
|
-
def rename_questions(
|
172
|
-
self, rename_dict: Dict[str, str], ignore_missing=False
|
173
|
-
) -> "InputData":
|
154
|
+
def rename_questions(self, rename_dict: Dict[str, str]) -> "InputData":
|
174
155
|
"""Rename a question.
|
175
156
|
|
176
157
|
>>> id = InputDataABC.example()
|
@@ -179,10 +160,10 @@ class InputDataABC(
|
|
179
160
|
|
180
161
|
"""
|
181
162
|
for old_name, new_name in rename_dict.items():
|
182
|
-
self.rename(old_name, new_name
|
163
|
+
self.rename(old_name, new_name)
|
183
164
|
return self
|
184
165
|
|
185
|
-
def rename(self, old_name, new_name
|
166
|
+
def rename(self, old_name, new_name) -> "InputData":
|
186
167
|
"""Rename a question.
|
187
168
|
|
188
169
|
>>> id = InputDataABC.example()
|
@@ -190,19 +171,13 @@ class InputDataABC(
|
|
190
171
|
['evening', 'feeling']
|
191
172
|
|
192
173
|
"""
|
193
|
-
if old_name not in self.question_names:
|
194
|
-
if ignore_missing:
|
195
|
-
return self
|
196
|
-
else:
|
197
|
-
raise ValueError(f"Question {old_name} not found.")
|
198
|
-
|
199
174
|
idx = self.question_names.index(old_name)
|
200
175
|
self.question_names[idx] = new_name
|
201
176
|
self.answer_codebook[new_name] = self.answer_codebook.pop(old_name, {})
|
202
177
|
|
203
178
|
return self
|
204
179
|
|
205
|
-
def _drop_question(self, question_name
|
180
|
+
def _drop_question(self, question_name):
|
206
181
|
"""Drop a question
|
207
182
|
|
208
183
|
>>> id = InputDataABC.example()
|
@@ -210,11 +185,6 @@ class InputDataABC(
|
|
210
185
|
['feeling']
|
211
186
|
|
212
187
|
"""
|
213
|
-
if question_name not in self.question_names:
|
214
|
-
if ignore_missing:
|
215
|
-
return self
|
216
|
-
else:
|
217
|
-
raise ValueError(f"Question {question_name} not found.")
|
218
188
|
idx = self.question_names.index(question_name)
|
219
189
|
self._question_names.pop(idx)
|
220
190
|
self._question_texts.pop(idx)
|
@@ -236,7 +206,7 @@ class InputDataABC(
|
|
236
206
|
self._drop_question(qn)
|
237
207
|
return self
|
238
208
|
|
239
|
-
def keep(self, *question_names_to_keep
|
209
|
+
def keep(self, *question_names_to_keep) -> "InputDataABC":
|
240
210
|
"""Keep a question.
|
241
211
|
|
242
212
|
>>> id = InputDataABC.example()
|
@@ -247,7 +217,7 @@ class InputDataABC(
|
|
247
217
|
all_question_names = self._question_names[:]
|
248
218
|
for qn in all_question_names:
|
249
219
|
if qn not in question_names_to_keep:
|
250
|
-
self._drop_question(qn
|
220
|
+
self._drop_question(qn)
|
251
221
|
return self
|
252
222
|
|
253
223
|
def modify_question_type(
|
@@ -314,7 +284,6 @@ class InputDataABC(
|
|
314
284
|
"raw_data": self.raw_data,
|
315
285
|
"question_names": self.question_names,
|
316
286
|
"question_texts": self.question_texts,
|
317
|
-
"binary": self.binary,
|
318
287
|
"answer_codebook": self.answer_codebook,
|
319
288
|
"question_types": self.question_types,
|
320
289
|
}
|