edsl 0.1.29.dev3__py3-none-any.whl → 0.1.29.dev6__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 +12 -15
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +37 -2
- edsl/agents/AgentList.py +3 -4
- edsl/agents/InvigilatorBase.py +15 -10
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/conjure/InputData.py +39 -8
- edsl/coop/coop.py +187 -150
- edsl/coop/utils.py +17 -76
- edsl/jobs/Jobs.py +23 -17
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +1 -0
- edsl/notebooks/Notebook.py +31 -0
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +32 -11
- edsl/questions/question_registry.py +20 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/ResultsToolsMixin.py +4 -1
- edsl/scenarios/ScenarioList.py +17 -3
- edsl/study/Study.py +3 -9
- edsl/surveys/Survey.py +37 -3
- edsl/tools/plotting.py +4 -2
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.29.dev6.dist-info}/METADATA +11 -10
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.29.dev6.dist-info}/RECORD +26 -27
- edsl-0.1.29.dev3.dist-info/entry_points.txt +0 -3
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.29.dev6.dist-info}/LICENSE +0 -0
- {edsl-0.1.29.dev3.dist-info → edsl-0.1.29.dev6.dist-info}/WHEEL +0 -0
@@ -1,134 +1,376 @@
|
|
1
|
-
from typing import Dict, Any
|
1
|
+
from typing import Dict, Any, Optional
|
2
|
+
from collections import UserList
|
2
3
|
|
4
|
+
# from functools import reduce
|
3
5
|
from edsl.prompts.Prompt import Prompt
|
4
|
-
|
6
|
+
|
7
|
+
# from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
5
8
|
from edsl.prompts.registry import get_classes as prompt_lookup
|
6
9
|
from edsl.exceptions import QuestionScenarioRenderError
|
7
10
|
|
11
|
+
import enum
|
8
12
|
|
9
|
-
class PromptConstructorMixin:
|
10
|
-
def construct_system_prompt(self) -> Prompt:
|
11
|
-
"""Construct the system prompt for the LLM call."""
|
12
13
|
|
13
|
-
|
14
|
-
|
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"
|
15
19
|
|
16
|
-
return (
|
17
|
-
agent_instructions
|
18
|
-
+ " " * int(len(persona_prompt.text) > 0)
|
19
|
-
+ persona_prompt
|
20
|
-
)
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
class PromptList(UserList):
|
22
|
+
separator = Prompt(" ")
|
23
|
+
|
24
|
+
def reduce(self):
|
25
|
+
"""Reduce the list of prompts to a single prompt.
|
24
26
|
|
25
|
-
|
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}\""")
|
26
30
|
|
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
|
30
31
|
"""
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
+
|
81
|
+
if isinstance(system_prompt_order, str):
|
82
|
+
system_prompt_order = (system_prompt_order,)
|
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__}"
|
35
92
|
)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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}"
|
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."
|
49
99
|
)
|
50
100
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
55
106
|
)
|
56
107
|
|
57
|
-
|
58
|
-
|
59
|
-
|
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)
|
112
|
+
|
113
|
+
def arrange_components(self, **kwargs) -> Dict[PromptComponent, Prompt]:
|
114
|
+
"""Arrange the components in the order specified by the plan."""
|
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}"
|
60
120
|
)
|
61
|
-
return persona_prompt
|
62
121
|
|
63
|
-
|
64
|
-
|
65
|
-
applicable_prompts = prompt_lookup(
|
66
|
-
component_type="agent_instructions",
|
67
|
-
model=self.model.model,
|
122
|
+
user_prompt = PromptList(
|
123
|
+
[kwargs[component.value] for component in self.user_prompt_order]
|
68
124
|
)
|
69
|
-
|
70
|
-
|
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
|
125
|
+
system_prompt = PromptList(
|
126
|
+
[kwargs[component.value] for component in self.system_prompt_order]
|
86
127
|
)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
+
|
148
|
+
This is mixed into the Invigilator class.
|
149
|
+
"""
|
150
|
+
|
151
|
+
prompt_plan = PromptPlan()
|
152
|
+
|
153
|
+
@property
|
154
|
+
def agent_instructions_prompt(self) -> Prompt:
|
155
|
+
"""
|
156
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
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"):
|
162
|
+
applicable_prompts = prompt_lookup(
|
163
|
+
component_type="agent_instructions",
|
164
|
+
model=self.model.model,
|
91
165
|
)
|
166
|
+
if len(applicable_prompts) == 0:
|
167
|
+
raise Exception("No applicable prompts found")
|
168
|
+
self._agent_instructions_prompt = applicable_prompts[0](
|
169
|
+
text=self.agent.instruction
|
170
|
+
)
|
171
|
+
return self._agent_instructions_prompt
|
92
172
|
|
93
|
-
|
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
|
+
|
234
|
+
>>> from edsl import QuestionFreeText
|
235
|
+
>>> q = QuestionFreeText(question_text = "Consider {{ X }}. What is your favorite color?", question_name = "q_color")
|
236
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
237
|
+
>>> i = InvigilatorBase.example(question = q)
|
238
|
+
>>> i.question_instructions_prompt
|
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
|
277
|
+
|
278
|
+
rendered_instructions = question_prompt.render(
|
279
|
+
self.question.data | self.scenario | d | {"agent": self.agent}
|
280
|
+
)
|
281
|
+
|
282
|
+
undefined_template_variables = (
|
283
|
+
rendered_instructions.undefined_template_variables({})
|
284
|
+
)
|
285
|
+
|
286
|
+
# Check if it's the name of a question in the survey
|
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"]
|
94
324
|
|
95
325
|
def construct_user_prompt(self) -> Prompt:
|
96
326
|
"""Construct the user prompt for the LLM call."""
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
return user_prompt
|
327
|
+
import warnings
|
328
|
+
|
329
|
+
warnings.warn(
|
330
|
+
"This method is deprecated. Use get_prompts instead.", DeprecationWarning
|
331
|
+
)
|
332
|
+
return self.get_prompts()["user_prompt"]
|
103
333
|
|
104
334
|
def get_prompts(self) -> Dict[str, Prompt]:
|
105
|
-
"""Get both prompts for the LLM call.
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
335
|
+
"""Get both prompts for the LLM call.
|
336
|
+
|
337
|
+
>>> from edsl import QuestionFreeText
|
338
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
339
|
+
>>> q = QuestionFreeText(question_text="How are you today?", question_name="q0")
|
340
|
+
>>> i = InvigilatorBase.example(question = q)
|
341
|
+
>>> i.get_prompts()
|
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
|
+
|
112
358
|
if hasattr(self.scenario, "has_image") and self.scenario.has_image:
|
113
359
|
prompts["encoded_image"] = self.scenario["encoded_image"]
|
114
360
|
return prompts
|
115
361
|
|
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
|
+
|
116
372
|
|
117
373
|
if __name__ == "__main__":
|
118
|
-
|
119
|
-
|
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)
|
374
|
+
import doctest
|
375
|
+
|
376
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/conjure/InputData.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
|
1
|
+
import base64
|
3
2
|
from abc import ABC, abstractmethod
|
4
3
|
from typing import Dict, Callable, Optional, List, Generator, Tuple, Union
|
5
4
|
from collections import namedtuple
|
@@ -52,6 +51,7 @@ class InputDataABC(
|
|
52
51
|
config: Optional[dict] = None,
|
53
52
|
naming_function: Optional[Callable] = sanitize_string,
|
54
53
|
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,6 +83,15 @@ 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
|
+
|
86
95
|
def default_repair_func(x):
|
87
96
|
return (
|
88
97
|
x.replace("#", "_num")
|
@@ -118,6 +127,14 @@ class InputDataABC(
|
|
118
127
|
if order_options:
|
119
128
|
self.order_options()
|
120
129
|
|
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
|
+
|
121
138
|
@abstractmethod
|
122
139
|
def get_question_texts(self) -> List[str]:
|
123
140
|
"""Get the text of the questions
|
@@ -151,7 +168,9 @@ class InputDataABC(
|
|
151
168
|
"""
|
152
169
|
raise NotImplementedError
|
153
170
|
|
154
|
-
def rename_questions(
|
171
|
+
def rename_questions(
|
172
|
+
self, rename_dict: Dict[str, str], ignore_missing=False
|
173
|
+
) -> "InputData":
|
155
174
|
"""Rename a question.
|
156
175
|
|
157
176
|
>>> id = InputDataABC.example()
|
@@ -160,10 +179,10 @@ class InputDataABC(
|
|
160
179
|
|
161
180
|
"""
|
162
181
|
for old_name, new_name in rename_dict.items():
|
163
|
-
self.rename(old_name, new_name)
|
182
|
+
self.rename(old_name, new_name, ignore_missing=ignore_missing)
|
164
183
|
return self
|
165
184
|
|
166
|
-
def rename(self, old_name, new_name) -> "InputData":
|
185
|
+
def rename(self, old_name, new_name, ignore_missing=False) -> "InputData":
|
167
186
|
"""Rename a question.
|
168
187
|
|
169
188
|
>>> id = InputDataABC.example()
|
@@ -171,13 +190,19 @@ class InputDataABC(
|
|
171
190
|
['evening', 'feeling']
|
172
191
|
|
173
192
|
"""
|
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
|
+
|
174
199
|
idx = self.question_names.index(old_name)
|
175
200
|
self.question_names[idx] = new_name
|
176
201
|
self.answer_codebook[new_name] = self.answer_codebook.pop(old_name, {})
|
177
202
|
|
178
203
|
return self
|
179
204
|
|
180
|
-
def _drop_question(self, question_name):
|
205
|
+
def _drop_question(self, question_name, ignore_missing=False):
|
181
206
|
"""Drop a question
|
182
207
|
|
183
208
|
>>> id = InputDataABC.example()
|
@@ -185,6 +210,11 @@ class InputDataABC(
|
|
185
210
|
['feeling']
|
186
211
|
|
187
212
|
"""
|
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.")
|
188
218
|
idx = self.question_names.index(question_name)
|
189
219
|
self._question_names.pop(idx)
|
190
220
|
self._question_texts.pop(idx)
|
@@ -206,7 +236,7 @@ class InputDataABC(
|
|
206
236
|
self._drop_question(qn)
|
207
237
|
return self
|
208
238
|
|
209
|
-
def keep(self, *question_names_to_keep) -> "InputDataABC":
|
239
|
+
def keep(self, *question_names_to_keep, ignore_missing=False) -> "InputDataABC":
|
210
240
|
"""Keep a question.
|
211
241
|
|
212
242
|
>>> id = InputDataABC.example()
|
@@ -217,7 +247,7 @@ class InputDataABC(
|
|
217
247
|
all_question_names = self._question_names[:]
|
218
248
|
for qn in all_question_names:
|
219
249
|
if qn not in question_names_to_keep:
|
220
|
-
self._drop_question(qn)
|
250
|
+
self._drop_question(qn, ignore_missing=ignore_missing)
|
221
251
|
return self
|
222
252
|
|
223
253
|
def modify_question_type(
|
@@ -284,6 +314,7 @@ class InputDataABC(
|
|
284
314
|
"raw_data": self.raw_data,
|
285
315
|
"question_names": self.question_names,
|
286
316
|
"question_texts": self.question_texts,
|
317
|
+
"binary": self.binary,
|
287
318
|
"answer_codebook": self.answer_codebook,
|
288
319
|
"question_types": self.question_types,
|
289
320
|
}
|