edsl 0.1.27.dev2__py3-none-any.whl → 0.1.29__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 +107 -30
- edsl/BaseDiff.py +260 -0
- edsl/__init__.py +25 -21
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +103 -46
- edsl/agents/AgentList.py +97 -13
- edsl/agents/Invigilator.py +23 -10
- edsl/agents/InvigilatorBase.py +19 -14
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +5 -2
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/AgentConstructionMixin.py +152 -0
- edsl/conjure/Conjure.py +56 -0
- edsl/conjure/InputData.py +659 -0
- edsl/conjure/InputDataCSV.py +48 -0
- edsl/conjure/InputDataMixinQuestionStats.py +182 -0
- edsl/conjure/InputDataPyRead.py +91 -0
- edsl/conjure/InputDataSPSS.py +8 -0
- edsl/conjure/InputDataStata.py +8 -0
- edsl/conjure/QuestionOptionMixin.py +76 -0
- edsl/conjure/QuestionTypeMixin.py +23 -0
- edsl/conjure/RawQuestion.py +65 -0
- edsl/conjure/SurveyResponses.py +7 -0
- edsl/conjure/__init__.py +9 -4
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/naming_utilities.py +263 -0
- edsl/conjure/utilities.py +165 -28
- edsl/conversation/Conversation.py +238 -0
- edsl/conversation/car_buying.py +58 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/coop.py +337 -121
- edsl/coop/utils.py +56 -70
- edsl/data/Cache.py +74 -22
- edsl/data/CacheHandler.py +10 -9
- edsl/data/SQLiteDict.py +11 -3
- edsl/inference_services/AnthropicService.py +1 -0
- edsl/inference_services/DeepInfraService.py +20 -13
- edsl/inference_services/GoogleService.py +7 -1
- edsl/inference_services/InferenceServicesCollection.py +33 -7
- edsl/inference_services/OpenAIService.py +17 -10
- edsl/inference_services/models_available_cache.py +69 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +322 -73
- edsl/jobs/buckets/BucketCollection.py +9 -3
- edsl/jobs/buckets/ModelBuckets.py +4 -2
- edsl/jobs/buckets/TokenBucket.py +1 -2
- edsl/jobs/interviews/Interview.py +7 -10
- edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
- edsl/jobs/interviews/retry_management.py +4 -4
- edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
- edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
- edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +42 -55
- edsl/language_models/ModelList.py +96 -0
- edsl/language_models/registry.py +14 -0
- edsl/language_models/repair.py +97 -25
- edsl/notebooks/Notebook.py +157 -32
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +145 -23
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +3 -3
- edsl/questions/QuestionFunctional.py +0 -3
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +16 -8
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +9 -4
- edsl/questions/question_registry.py +27 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +493 -0
- edsl/results/Result.py +42 -82
- edsl/results/Results.py +178 -66
- edsl/results/ResultsDBMixin.py +10 -9
- edsl/results/ResultsExportMixin.py +23 -507
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +9 -9
- edsl/scenarios/FileStore.py +140 -0
- edsl/scenarios/Scenario.py +59 -6
- edsl/scenarios/ScenarioList.py +138 -52
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +73 -0
- edsl/study/Study.py +498 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +124 -37
- edsl/surveys/SurveyExportMixin.py +25 -5
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/interface.py +90 -73
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/utilities.py +59 -6
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
- edsl-0.1.29.dist-info/RECORD +203 -0
- edsl/conjure/RawResponseColumn.py +0 -327
- edsl/conjure/SurveyBuilder.py +0 -308
- edsl/conjure/SurveyBuilderCSV.py +0 -78
- edsl/conjure/SurveyBuilderSPSS.py +0 -118
- edsl/data/RemoteDict.py +0 -103
- edsl-0.1.27.dev2.dist-info/RECORD +0 -172
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
edsl/questions/QuestionBase.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
"""This module contains the Question class, which is the base class for all questions in EDSL."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
|
+
import time
|
4
5
|
from abc import ABC, abstractmethod
|
5
|
-
from
|
6
|
-
|
6
|
+
from typing import Any, Type, Optional, List, Callable
|
7
|
+
import copy
|
7
8
|
|
8
9
|
from edsl.exceptions import (
|
9
10
|
QuestionResponseValidationError,
|
@@ -11,10 +12,11 @@ from edsl.exceptions import (
|
|
11
12
|
)
|
12
13
|
from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
13
14
|
|
14
|
-
|
15
|
+
|
15
16
|
from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
|
16
17
|
from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
|
17
18
|
from edsl.Base import PersistenceMixin, RichPrintingMixin
|
19
|
+
from edsl.BaseDiff import BaseDiff, BaseDiffCollection
|
18
20
|
|
19
21
|
from edsl.questions.SimpleAskMixin import SimpleAskMixin
|
20
22
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
@@ -37,6 +39,12 @@ class QuestionBase(
|
|
37
39
|
"""Get an attribute of the question."""
|
38
40
|
return getattr(self, key)
|
39
41
|
|
42
|
+
def __hash__(self) -> int:
|
43
|
+
"""Return a hash of the question."""
|
44
|
+
from edsl.utilities.utilities import dict_hash
|
45
|
+
|
46
|
+
return dict_hash(self._to_dict())
|
47
|
+
|
40
48
|
def _repr_html_(self):
|
41
49
|
from edsl.utilities.utilities import data_to_html
|
42
50
|
|
@@ -49,6 +57,35 @@ class QuestionBase(
|
|
49
57
|
|
50
58
|
return data_to_html(data)
|
51
59
|
|
60
|
+
def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
|
61
|
+
"""Apply a function to the question parts
|
62
|
+
|
63
|
+
>>> from edsl.questions import QuestionFreeText
|
64
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
65
|
+
>>> shouting = lambda x: x.upper()
|
66
|
+
>>> q.apply_function(shouting)
|
67
|
+
Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
|
68
|
+
|
69
|
+
"""
|
70
|
+
if exclude_components is None:
|
71
|
+
exclude_components = ["question_name", "question_type"]
|
72
|
+
|
73
|
+
d = copy.deepcopy(self._to_dict())
|
74
|
+
for key, value in d.items():
|
75
|
+
if key in exclude_components:
|
76
|
+
continue
|
77
|
+
if isinstance(value, dict):
|
78
|
+
for k, v in value.items():
|
79
|
+
value[k] = func(v)
|
80
|
+
d[key] = value
|
81
|
+
continue
|
82
|
+
if isinstance(value, list):
|
83
|
+
value = [func(v) for v in value]
|
84
|
+
d[key] = value
|
85
|
+
continue
|
86
|
+
d[key] = func(value)
|
87
|
+
return QuestionBase.from_dict(d)
|
88
|
+
|
52
89
|
@property
|
53
90
|
def data(self) -> dict:
|
54
91
|
"""Return a dictionary of question attributes **except** for question_type."""
|
@@ -87,6 +124,8 @@ class QuestionBase(
|
|
87
124
|
:param model: The language model to use. If None, assumes does not matter.
|
88
125
|
|
89
126
|
"""
|
127
|
+
from edsl.prompts.registry import get_classes as prompt_lookup
|
128
|
+
|
90
129
|
applicable_prompts = prompt_lookup(
|
91
130
|
component_type="question_instructions",
|
92
131
|
question_type=cls.question_type,
|
@@ -101,6 +140,33 @@ class QuestionBase(
|
|
101
140
|
self._model_instructions = {}
|
102
141
|
return self._model_instructions
|
103
142
|
|
143
|
+
def _all_text(self) -> str:
|
144
|
+
"""Return the question text."""
|
145
|
+
txt = ""
|
146
|
+
for key, value in self.data.items():
|
147
|
+
if isinstance(value, str):
|
148
|
+
txt += value
|
149
|
+
elif isinstance(value, list):
|
150
|
+
txt += "".join(str(value))
|
151
|
+
return txt
|
152
|
+
|
153
|
+
@property
|
154
|
+
def parameters(self) -> set[str]:
|
155
|
+
"""Return the parameters of the question."""
|
156
|
+
from jinja2 import Environment, meta
|
157
|
+
|
158
|
+
env = Environment()
|
159
|
+
# Parse the template
|
160
|
+
txt = self._all_text()
|
161
|
+
# txt = self.question_text
|
162
|
+
# if hasattr(self, "question_options"):
|
163
|
+
# txt += " ".join(self.question_options)
|
164
|
+
parsed_content = env.parse(txt)
|
165
|
+
# Extract undeclared variables
|
166
|
+
variables = meta.find_undeclared_variables(parsed_content)
|
167
|
+
# Return as a list
|
168
|
+
return set(variables)
|
169
|
+
|
104
170
|
@model_instructions.setter
|
105
171
|
def model_instructions(self, data: dict):
|
106
172
|
"""Set the model-specific instructions for the question."""
|
@@ -109,15 +175,16 @@ class QuestionBase(
|
|
109
175
|
def add_model_instructions(
|
110
176
|
self, *, instructions: str, model: Optional[str] = None
|
111
177
|
) -> None:
|
112
|
-
"""Add model-specific instructions for the question.
|
178
|
+
"""Add model-specific instructions for the question that override the default instructions.
|
113
179
|
|
114
180
|
:param instructions: The instructions to add. This is typically a jinja2 template.
|
115
181
|
:param model: The language model for this instruction.
|
116
182
|
|
117
183
|
>>> from edsl.questions import QuestionFreeText
|
118
184
|
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
119
|
-
>>> q.add_model_instructions(instructions = "Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
|
120
|
-
|
185
|
+
>>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
|
186
|
+
>>> q.get_instructions(model = "gpt3")
|
187
|
+
Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
|
121
188
|
"""
|
122
189
|
from edsl import Model
|
123
190
|
|
@@ -137,6 +204,13 @@ class QuestionBase(
|
|
137
204
|
"""Get the mathcing question-answering instructions for the question.
|
138
205
|
|
139
206
|
:param model: The language model to use.
|
207
|
+
|
208
|
+
>>> from edsl import QuestionFreeText
|
209
|
+
>>> QuestionFreeText.example().get_instructions()
|
210
|
+
Prompt(text=\"""You are being asked the following question: {{question_text}}
|
211
|
+
Return a valid JSON formatted like this:
|
212
|
+
{"answer": "<put free text answer here>"}
|
213
|
+
\""")
|
140
214
|
"""
|
141
215
|
from edsl.prompts.Prompt import Prompt
|
142
216
|
|
@@ -167,13 +241,17 @@ class QuestionBase(
|
|
167
241
|
############################
|
168
242
|
# Serialization methods
|
169
243
|
############################
|
170
|
-
|
171
|
-
def to_dict(self) -> dict[str, Any]:
|
244
|
+
def _to_dict(self):
|
172
245
|
"""Convert the question to a dictionary that includes the question type (used in deserialization)."""
|
173
246
|
candidate_data = self.data.copy()
|
174
247
|
candidate_data["question_type"] = self.question_type
|
175
248
|
return candidate_data
|
176
249
|
|
250
|
+
@add_edsl_version
|
251
|
+
def to_dict(self) -> dict[str, Any]:
|
252
|
+
"""Convert the question to a dictionary that includes the question type (used in deserialization)."""
|
253
|
+
return self._to_dict()
|
254
|
+
|
177
255
|
@classmethod
|
178
256
|
@remove_edsl_version
|
179
257
|
def from_dict(cls, data: dict) -> Type[QuestionBase]:
|
@@ -211,6 +289,10 @@ class QuestionBase(
|
|
211
289
|
|
212
290
|
return question_class(**local_data)
|
213
291
|
|
292
|
+
def copy(self) -> Type[QuestionBase]:
|
293
|
+
"""Return a deep copy of the question."""
|
294
|
+
return copy.deepcopy(self)
|
295
|
+
|
214
296
|
############################
|
215
297
|
# Dunder methods
|
216
298
|
############################
|
@@ -220,6 +302,33 @@ class QuestionBase(
|
|
220
302
|
|
221
303
|
print_json(json.dumps(self.to_dict()))
|
222
304
|
|
305
|
+
def __call__(self, just_answer=True, model=None, agent=None, **kwargs):
|
306
|
+
"""Call the question.
|
307
|
+
|
308
|
+
>>> from edsl.language_models import LanguageModel
|
309
|
+
>>> m = LanguageModel.example(canned_response = "Yo, what's up?", test_model = True)
|
310
|
+
>>> from edsl import QuestionFreeText
|
311
|
+
>>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
|
312
|
+
>>> q(model = m)
|
313
|
+
"Yo, what's up?"
|
314
|
+
|
315
|
+
"""
|
316
|
+
survey = self.to_survey()
|
317
|
+
results = survey(model=model, agent=agent, **kwargs)
|
318
|
+
if just_answer:
|
319
|
+
return results.select(f"answer.{self.question_name}").first()
|
320
|
+
else:
|
321
|
+
return results
|
322
|
+
|
323
|
+
async def run_async(self, just_answer=True, model=None, agent=None, **kwargs):
|
324
|
+
"""Call the question."""
|
325
|
+
survey = self.to_survey()
|
326
|
+
results = await survey.run_async(model=model, agent=agent, **kwargs)
|
327
|
+
if just_answer:
|
328
|
+
return results.select(f"answer.{self.question_name}").first()
|
329
|
+
else:
|
330
|
+
return results
|
331
|
+
|
223
332
|
def __repr__(self) -> str:
|
224
333
|
"""Return a string representation of the question. Should be able to be used to reconstruct the question."""
|
225
334
|
class_name = self.__class__.__name__
|
@@ -237,21 +346,27 @@ class QuestionBase(
|
|
237
346
|
return False
|
238
347
|
return self.to_dict() == other.to_dict()
|
239
348
|
|
349
|
+
def __sub__(self, other) -> BaseDiff:
|
350
|
+
"""Return the difference between two objects."""
|
351
|
+
|
352
|
+
return BaseDiff(other, self)
|
353
|
+
|
240
354
|
# TODO: Throws an error that should be addressed at QuestionFunctional
|
241
|
-
def __add__(self,
|
355
|
+
def __add__(self, other_question_or_diff):
|
242
356
|
"""
|
243
357
|
Compose two questions into a single question.
|
244
358
|
|
245
|
-
|
246
|
-
|
247
|
-
>>> from edsl.questions.QuestionNumerical import QuestionNumerical
|
248
|
-
>>> q1 = QuestionFreeText(question_text = "What is the capital of {{country}}", question_name = "capital")
|
249
|
-
>>> q2 = QuestionNumerical(question_text = "What is the population of {{capital}}, in millions. Please round", question_name = "population")
|
250
|
-
>>> q3 = q1 + q2
|
359
|
+
TODO: Probably getting deprecated.
|
360
|
+
|
251
361
|
"""
|
362
|
+
if isinstance(other_question_or_diff, BaseDiff) or isinstance(
|
363
|
+
other_question_or_diff, BaseDiffCollection
|
364
|
+
):
|
365
|
+
return other_question_or_diff.apply(self)
|
366
|
+
|
252
367
|
from edsl.questions import compose_questions
|
253
368
|
|
254
|
-
return compose_questions(self,
|
369
|
+
return compose_questions(self, other_question_or_diff)
|
255
370
|
|
256
371
|
@abstractmethod
|
257
372
|
def _validate_answer(self, answer: dict[str, str]):
|
@@ -279,36 +394,41 @@ class QuestionBase(
|
|
279
394
|
############################
|
280
395
|
# Forward methods
|
281
396
|
############################
|
282
|
-
def add_question(self, other:
|
397
|
+
def add_question(self, other: QuestionBase) -> "Survey":
|
283
398
|
"""Add a question to this question by turning them into a survey with two questions."""
|
284
399
|
from edsl.surveys.Survey import Survey
|
285
400
|
|
286
401
|
s = Survey([self, other])
|
287
402
|
return s
|
288
403
|
|
289
|
-
def to_survey(self):
|
404
|
+
def to_survey(self) -> "Survey":
|
290
405
|
"""Turn a single question into a survey."""
|
291
406
|
from edsl.surveys.Survey import Survey
|
292
407
|
|
293
408
|
s = Survey([self])
|
294
409
|
return s
|
295
410
|
|
296
|
-
def run(self, *args, **kwargs):
|
411
|
+
def run(self, *args, **kwargs) -> "Results":
|
297
412
|
"""Turn a single question into a survey and run it."""
|
298
413
|
from edsl.surveys.Survey import Survey
|
299
414
|
|
300
415
|
s = self.to_survey()
|
301
416
|
return s.run(*args, **kwargs)
|
302
417
|
|
303
|
-
def by(self, *args):
|
304
|
-
"""Turn a single question into a survey and
|
418
|
+
def by(self, *args) -> "Jobs":
|
419
|
+
"""Turn a single question into a survey and then a Job."""
|
305
420
|
from edsl.surveys.Survey import Survey
|
306
421
|
|
307
422
|
s = Survey([self])
|
308
423
|
return s.by(*args)
|
309
424
|
|
310
|
-
def human_readable(self):
|
311
|
-
"""Print the question in a human readable format.
|
425
|
+
def human_readable(self) -> str:
|
426
|
+
"""Print the question in a human readable format.
|
427
|
+
|
428
|
+
>>> from edsl.questions import QuestionFreeText
|
429
|
+
>>> QuestionFreeText.example().human_readable()
|
430
|
+
'Question Type: free_text\\nQuestion: How are you?'
|
431
|
+
"""
|
312
432
|
lines = []
|
313
433
|
lines.append(f"Question Type: {self.question_type}")
|
314
434
|
lines.append(f"Question: {self.question_text}")
|
@@ -378,6 +498,8 @@ class QuestionBase(
|
|
378
498
|
|
379
499
|
def rich_print(self):
|
380
500
|
"""Print the question in a rich format."""
|
501
|
+
from rich.table import Table
|
502
|
+
|
381
503
|
table = Table(show_header=True, header_style="bold magenta")
|
382
504
|
table.add_column("Question Name", style="dim")
|
383
505
|
table.add_column("Question Type")
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import random
|
3
|
-
import textwrap
|
4
3
|
from typing import Any, Optional, Union
|
5
4
|
from edsl.questions.QuestionBase import QuestionBase
|
6
5
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
7
|
-
from edsl.scenarios import Scenario
|
8
|
-
from edsl.utilities import random_string
|
9
6
|
|
10
7
|
|
11
8
|
class QuestionBudget(QuestionBase):
|
@@ -46,7 +43,7 @@ class QuestionBudget(QuestionBase):
|
|
46
43
|
return answer
|
47
44
|
|
48
45
|
def _translate_answer_code_to_answer(
|
49
|
-
self, answer_codes: dict[str, int], scenario: Scenario = None
|
46
|
+
self, answer_codes: dict[str, int], scenario: "Scenario" = None
|
50
47
|
):
|
51
48
|
"""
|
52
49
|
Translate the answer codes to the actual answers.
|
@@ -63,6 +60,8 @@ class QuestionBudget(QuestionBase):
|
|
63
60
|
|
64
61
|
def _simulate_answer(self, human_readable=True):
|
65
62
|
"""Simulate a valid answer for debugging purposes (what the validator expects)."""
|
63
|
+
from edsl.utilities.utilities import random_string
|
64
|
+
|
66
65
|
if human_readable:
|
67
66
|
keys = self.question_options
|
68
67
|
else:
|
@@ -163,8 +162,8 @@ def main():
|
|
163
162
|
|
164
163
|
|
165
164
|
if __name__ == "__main__":
|
166
|
-
q = QuestionBudget.example()
|
167
|
-
results = q.run()
|
165
|
+
# q = QuestionBudget.example()
|
166
|
+
# results = q.run()
|
168
167
|
|
169
168
|
import doctest
|
170
169
|
|
@@ -9,8 +9,6 @@ from edsl.questions.descriptors import (
|
|
9
9
|
IntegerDescriptor,
|
10
10
|
QuestionOptionsDescriptor,
|
11
11
|
)
|
12
|
-
from edsl.scenarios import Scenario
|
13
|
-
from edsl.utilities import random_string
|
14
12
|
|
15
13
|
|
16
14
|
class QuestionCheckBox(QuestionBase):
|
@@ -55,13 +53,17 @@ class QuestionCheckBox(QuestionBase):
|
|
55
53
|
self._validate_answer_checkbox(answer)
|
56
54
|
return answer
|
57
55
|
|
58
|
-
def _translate_answer_code_to_answer(
|
56
|
+
def _translate_answer_code_to_answer(
|
57
|
+
self, answer_codes, scenario: "Scenario" = None
|
58
|
+
):
|
59
59
|
"""
|
60
60
|
Translate the answer code to the actual answer.
|
61
61
|
|
62
62
|
For example, for question options ["a", "b", "c"],the answer codes are 0, 1, and 2.
|
63
63
|
The LLM will respond with [0,1] and this code will translate it to ["a","b"].
|
64
64
|
"""
|
65
|
+
from edsl.scenarios.Scenario import Scenario
|
66
|
+
|
65
67
|
scenario = scenario or Scenario()
|
66
68
|
translated_options = [
|
67
69
|
Template(option).render(scenario) for option in self.question_options
|
@@ -73,6 +75,8 @@ class QuestionCheckBox(QuestionBase):
|
|
73
75
|
|
74
76
|
def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
75
77
|
"""Simulate a valid answer for debugging purposes."""
|
78
|
+
from edsl.utilities.utilities import random_string
|
79
|
+
|
76
80
|
min_selections = self.min_selections or 1
|
77
81
|
max_selections = self.max_selections or len(self.question_options)
|
78
82
|
num_selections = random.randint(min_selections, max_selections)
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
2
2
|
from typing import Any
|
3
3
|
from edsl.questions.QuestionBase import QuestionBase
|
4
4
|
from edsl.questions.descriptors import AnswerTemplateDescriptor
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
5
|
|
8
6
|
|
9
7
|
class QuestionExtract(QuestionBase):
|
@@ -44,12 +42,14 @@ class QuestionExtract(QuestionBase):
|
|
44
42
|
self._validate_answer_extract(answer)
|
45
43
|
return answer
|
46
44
|
|
47
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
45
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
48
46
|
"""Return the answer in a human-readable format."""
|
49
47
|
return answer
|
50
48
|
|
51
49
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
52
50
|
"""Simulate a valid answer for debugging purposes."""
|
51
|
+
from edsl.utilities.utilities import random_string
|
52
|
+
|
53
53
|
return {
|
54
54
|
"answer": {key: random_string() for key in self.answer_template.keys()},
|
55
55
|
"comment": random_string(),
|
@@ -106,6 +106,8 @@ def main():
|
|
106
106
|
q.to_dict()
|
107
107
|
assert q.from_dict(q.to_dict()) == q
|
108
108
|
|
109
|
+
|
110
|
+
if __name__ == "__main__":
|
109
111
|
import doctest
|
110
112
|
|
111
113
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
2
2
|
import textwrap
|
3
3
|
from typing import Any, Optional
|
4
4
|
from edsl.questions.QuestionBase import QuestionBase
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
5
|
|
8
6
|
|
9
7
|
class QuestionFreeText(QuestionBase):
|
@@ -43,12 +41,14 @@ class QuestionFreeText(QuestionBase):
|
|
43
41
|
self._validate_answer_key_value(answer, "answer", str)
|
44
42
|
return answer
|
45
43
|
|
46
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
44
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
47
45
|
"""Do nothing, because the answer is already in a human-readable format."""
|
48
46
|
return answer
|
49
47
|
|
50
48
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
51
49
|
"""Simulate a valid answer for debugging purposes."""
|
50
|
+
from edsl.utilities.utilities import random_string
|
51
|
+
|
52
52
|
return {"answer": random_string()}
|
53
53
|
|
54
54
|
@property
|
@@ -1,10 +1,7 @@
|
|
1
1
|
from typing import Optional, Callable
|
2
|
-
from edsl.questions.QuestionBase import QuestionBase
|
3
|
-
from edsl.questions.descriptors import FunctionDescriptor
|
4
2
|
import inspect
|
5
3
|
|
6
4
|
from edsl.questions.QuestionBase import QuestionBase
|
7
|
-
from edsl.questions.descriptors import FunctionDescriptor
|
8
5
|
|
9
6
|
from edsl.utilities.restricted_python import create_restricted_function
|
10
7
|
|
edsl/questions/QuestionList.py
CHANGED
@@ -5,9 +5,6 @@ from typing import Any, Optional, Union
|
|
5
5
|
from edsl.questions.QuestionBase import QuestionBase
|
6
6
|
from edsl.questions.descriptors import IntegerOrNoneDescriptor
|
7
7
|
|
8
|
-
from edsl.scenarios import Scenario
|
9
|
-
from edsl.utilities import random_string
|
10
|
-
|
11
8
|
|
12
9
|
class QuestionList(QuestionBase):
|
13
10
|
"""This question prompts the agent to answer by providing a list of items as comma-separated strings."""
|
@@ -42,13 +39,15 @@ class QuestionList(QuestionBase):
|
|
42
39
|
self._validate_answer_list(answer)
|
43
40
|
return answer
|
44
41
|
|
45
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
42
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
46
43
|
"""There is no answer code."""
|
47
44
|
return answer
|
48
45
|
|
49
46
|
def _simulate_answer(self, human_readable: bool = True):
|
50
47
|
"""Simulate a valid answer for debugging purposes (what the validator expects)."""
|
51
48
|
num_items = random.randint(1, self.max_list_items or 2)
|
49
|
+
from edsl.utilities.utilities import random_string
|
50
|
+
|
52
51
|
return {"answer": [random_string() for _ in range(num_items)]}
|
53
52
|
|
54
53
|
@property
|
@@ -1,13 +1,12 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
import time
|
3
|
+
from typing import Union
|
3
4
|
import random
|
4
5
|
|
5
6
|
from jinja2 import Template
|
6
7
|
|
7
|
-
from edsl.utilities import random_string
|
8
|
-
from edsl.questions.descriptors import QuestionOptionsDescriptor
|
9
8
|
from edsl.questions.QuestionBase import QuestionBase
|
10
|
-
from edsl.
|
9
|
+
from edsl.questions.descriptors import QuestionOptionsDescriptor
|
11
10
|
|
12
11
|
|
13
12
|
class QuestionMultipleChoice(QuestionBase):
|
@@ -15,9 +14,9 @@ class QuestionMultipleChoice(QuestionBase):
|
|
15
14
|
|
16
15
|
question_type = "multiple_choice"
|
17
16
|
purpose = "When options are known and limited"
|
18
|
-
question_options: Union[
|
19
|
-
|
20
|
-
|
17
|
+
question_options: Union[list[str], list[list], list[float], list[int]] = (
|
18
|
+
QuestionOptionsDescriptor()
|
19
|
+
)
|
21
20
|
|
22
21
|
def __init__(
|
23
22
|
self,
|
@@ -47,8 +46,12 @@ class QuestionMultipleChoice(QuestionBase):
|
|
47
46
|
self._validate_answer_multiple_choice(answer)
|
48
47
|
return answer
|
49
48
|
|
50
|
-
def _translate_answer_code_to_answer(
|
49
|
+
def _translate_answer_code_to_answer(
|
50
|
+
self, answer_code, scenario: "Scenario" = None
|
51
|
+
):
|
51
52
|
"""Translate the answer code to the actual answer."""
|
53
|
+
from edsl.scenarios.Scenario import Scenario
|
54
|
+
|
52
55
|
scenario = scenario or Scenario()
|
53
56
|
translated_options = [
|
54
57
|
Template(str(option)).render(scenario) for option in self.question_options
|
@@ -59,6 +62,8 @@ class QuestionMultipleChoice(QuestionBase):
|
|
59
62
|
self, human_readable: bool = True
|
60
63
|
) -> dict[str, Union[int, str]]:
|
61
64
|
"""Simulate a valid answer for debugging purposes."""
|
65
|
+
from edsl.utilities.utilities import random_string
|
66
|
+
|
62
67
|
if human_readable:
|
63
68
|
answer = random.choice(self.question_options)
|
64
69
|
else:
|
@@ -70,6 +75,7 @@ class QuestionMultipleChoice(QuestionBase):
|
|
70
75
|
|
71
76
|
@property
|
72
77
|
def question_html_content(self) -> str:
|
78
|
+
|
73
79
|
if hasattr(self, "option_labels"):
|
74
80
|
option_labels = self.option_labels
|
75
81
|
else:
|
@@ -127,6 +133,8 @@ def main():
|
|
127
133
|
q.to_dict()
|
128
134
|
assert q.from_dict(q.to_dict()) == q
|
129
135
|
|
136
|
+
|
137
|
+
if __name__ == "__main__":
|
130
138
|
import doctest
|
131
139
|
|
132
140
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -2,11 +2,10 @@ from __future__ import annotations
|
|
2
2
|
import textwrap
|
3
3
|
from random import uniform
|
4
4
|
from typing import Any, Optional, Union
|
5
|
+
|
5
6
|
from edsl.exceptions import QuestionAnswerValidationError
|
6
7
|
from edsl.questions.QuestionBase import QuestionBase
|
7
8
|
from edsl.questions.descriptors import NumericalOrNoneDescriptor
|
8
|
-
from edsl.scenarios import Scenario
|
9
|
-
from edsl.utilities import random_string
|
10
9
|
|
11
10
|
|
12
11
|
class QuestionNumerical(QuestionBase):
|
@@ -48,12 +47,14 @@ class QuestionNumerical(QuestionBase):
|
|
48
47
|
self._validate_answer_numerical(answer)
|
49
48
|
return answer
|
50
49
|
|
51
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
50
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
52
51
|
"""There is no answer code."""
|
53
52
|
return answer
|
54
53
|
|
55
54
|
def _simulate_answer(self, human_readable: bool = True):
|
56
55
|
"""Simulate a valid answer for debugging purposes."""
|
56
|
+
from edsl.utilities.utilities import random_string
|
57
|
+
|
57
58
|
return {
|
58
59
|
"answer": uniform(self.min_value, self.max_value),
|
59
60
|
"comment": random_string(),
|
edsl/questions/QuestionRank.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import random
|
3
|
-
import textwrap
|
4
3
|
from jinja2 import Template
|
5
4
|
from typing import Any, Optional, Union
|
6
5
|
from edsl.questions.QuestionBase import QuestionBase
|
7
6
|
from edsl.exceptions import QuestionAnswerValidationError
|
8
|
-
|
9
|
-
from edsl.utilities.utilities import random_string
|
7
|
+
|
10
8
|
from edsl.questions.descriptors import (
|
11
9
|
QuestionOptionsDescriptor,
|
12
10
|
NumSelectionsDescriptor,
|
@@ -55,6 +53,8 @@ class QuestionRank(QuestionBase):
|
|
55
53
|
self, answer_codes, scenario: Scenario = None
|
56
54
|
) -> list[str]:
|
57
55
|
"""Translate the answer code to the actual answer."""
|
56
|
+
from edsl.scenarios import Scenario
|
57
|
+
|
58
58
|
scenario = scenario or Scenario()
|
59
59
|
translated_options = [
|
60
60
|
Template(option).render(scenario) for option in self.question_options
|
@@ -66,6 +66,8 @@ class QuestionRank(QuestionBase):
|
|
66
66
|
|
67
67
|
def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
68
68
|
"""Simulate a valid answer for debugging purposes."""
|
69
|
+
from edsl.utilities.utilities import random_string
|
70
|
+
|
69
71
|
if human_readable:
|
70
72
|
selected = random.sample(self.question_options, self.num_selections)
|
71
73
|
else:
|
edsl/questions/__init__.py
CHANGED
@@ -10,17 +10,18 @@ from edsl.questions.QuestionBudget import QuestionBudget
|
|
10
10
|
from edsl.questions.QuestionCheckBox import QuestionCheckBox
|
11
11
|
from edsl.questions.QuestionExtract import QuestionExtract
|
12
12
|
from edsl.questions.QuestionFreeText import QuestionFreeText
|
13
|
+
|
13
14
|
from edsl.questions.QuestionFunctional import QuestionFunctional
|
14
15
|
from edsl.questions.QuestionList import QuestionList
|
15
16
|
from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
|
16
17
|
from edsl.questions.QuestionNumerical import QuestionNumerical
|
17
18
|
from edsl.questions.QuestionRank import QuestionRank
|
18
19
|
|
19
|
-
# Questions derived from core questions
|
20
|
+
# # Questions derived from core questions
|
20
21
|
from edsl.questions.derived.QuestionLikertFive import QuestionLikertFive
|
21
22
|
from edsl.questions.derived.QuestionLinearScale import QuestionLinearScale
|
22
23
|
from edsl.questions.derived.QuestionTopK import QuestionTopK
|
23
24
|
from edsl.questions.derived.QuestionYesNo import QuestionYesNo
|
24
25
|
|
25
|
-
# Compose Questions
|
26
|
-
from edsl.questions.compose_questions import compose_questions
|
26
|
+
# # Compose Questions
|
27
|
+
# from edsl.questions.compose_questions import compose_questions
|
edsl/questions/descriptors.py
CHANGED
@@ -8,9 +8,7 @@ from edsl.exceptions import (
|
|
8
8
|
QuestionAnswerValidationError,
|
9
9
|
)
|
10
10
|
from edsl.questions.settings import Settings
|
11
|
-
from edsl.utilities.utilities import is_valid_variable_name
|
12
11
|
|
13
|
-
from edsl.prompts import get_classes
|
14
12
|
|
15
13
|
################################
|
16
14
|
# Helper functions
|
@@ -56,6 +54,8 @@ class BaseDescriptor(ABC):
|
|
56
54
|
def __set__(self, instance, value: Any) -> None:
|
57
55
|
"""Set the value of the attribute."""
|
58
56
|
self.validate(value, instance)
|
57
|
+
from edsl.prompts.registry import get_classes
|
58
|
+
|
59
59
|
instance.__dict__[self.name] = value
|
60
60
|
if self.name == "_instructions":
|
61
61
|
instructions = value
|
@@ -231,6 +231,8 @@ class QuestionNameDescriptor(BaseDescriptor):
|
|
231
231
|
|
232
232
|
def validate(self, value, instance):
|
233
233
|
"""Validate the value is a valid variable name."""
|
234
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
235
|
+
|
234
236
|
if not is_valid_variable_name(value):
|
235
237
|
raise QuestionCreationValidationError(
|
236
238
|
f"`question_name` is not a valid variable name (got {value})."
|
@@ -331,6 +333,9 @@ class QuestionTextDescriptor(BaseDescriptor):
|
|
331
333
|
if not isinstance(value, str):
|
332
334
|
raise Exception("Question must be a string!")
|
333
335
|
if contains_single_braced_substring(value):
|
334
|
-
|
335
|
-
|
336
|
+
import warnings
|
337
|
+
|
338
|
+
warnings.warn(
|
339
|
+
f"WARNING: Question text contains a single-braced substring: If you intended to parameterize the question with a Scenario this should be changed to a double-braced substring, e.g. {{variable}}.\nSee details on constructing Scenarios in the docs: https://docs.expectedparrot.com/en/latest/scenarios.html",
|
340
|
+
UserWarning,
|
336
341
|
)
|