edsl 0.1.33.dev2__py3-none-any.whl → 0.1.34__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 +24 -14
- edsl/__init__.py +1 -0
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +6 -6
- edsl/agents/Invigilator.py +28 -6
- edsl/agents/InvigilatorBase.py +8 -27
- edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +150 -182
- edsl/agents/prompt_helpers.py +129 -0
- edsl/config.py +26 -34
- edsl/coop/coop.py +14 -4
- edsl/data_transfer_models.py +26 -73
- edsl/enums.py +2 -0
- edsl/inference_services/AnthropicService.py +5 -2
- edsl/inference_services/AwsBedrock.py +5 -2
- edsl/inference_services/AzureAI.py +5 -2
- edsl/inference_services/GoogleService.py +108 -33
- edsl/inference_services/InferenceServiceABC.py +44 -13
- edsl/inference_services/MistralAIService.py +5 -2
- edsl/inference_services/OpenAIService.py +10 -6
- edsl/inference_services/TestService.py +34 -16
- edsl/inference_services/TogetherAIService.py +170 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/Jobs.py +109 -18
- edsl/jobs/buckets/BucketCollection.py +24 -15
- edsl/jobs/buckets/TokenBucket.py +64 -10
- edsl/jobs/interviews/Interview.py +130 -49
- edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +16 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +2 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +119 -173
- edsl/jobs/runners/JobsRunnerStatus.py +332 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +1 -13
- edsl/jobs/tasks/TaskHistory.py +17 -0
- edsl/language_models/LanguageModel.py +36 -38
- edsl/language_models/registry.py +13 -9
- edsl/language_models/utilities.py +5 -2
- edsl/questions/QuestionBase.py +74 -16
- edsl/questions/QuestionBaseGenMixin.py +28 -0
- edsl/questions/QuestionBudget.py +93 -41
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionFreeText.py +6 -0
- edsl/questions/QuestionMultipleChoice.py +13 -24
- edsl/questions/QuestionNumerical.py +5 -4
- edsl/questions/Quick.py +41 -0
- edsl/questions/ResponseValidatorABC.py +11 -6
- edsl/questions/derived/QuestionLinearScale.py +4 -1
- edsl/questions/derived/QuestionTopK.py +4 -1
- edsl/questions/derived/QuestionYesNo.py +8 -2
- edsl/questions/descriptors.py +12 -11
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +0 -1
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +2 -2
- edsl/results/DatasetExportMixin.py +5 -1
- edsl/results/Result.py +1 -1
- edsl/results/Results.py +4 -1
- edsl/scenarios/FileStore.py +178 -34
- edsl/scenarios/Scenario.py +76 -37
- edsl/scenarios/ScenarioList.py +19 -2
- edsl/scenarios/ScenarioListPdfMixin.py +150 -4
- edsl/study/Study.py +32 -0
- edsl/surveys/DAG.py +62 -0
- edsl/surveys/MemoryPlan.py +26 -0
- edsl/surveys/Rule.py +34 -1
- edsl/surveys/RuleCollection.py +55 -5
- edsl/surveys/Survey.py +189 -10
- edsl/surveys/base.py +4 -0
- edsl/templates/error_reporting/interview_details.html +6 -1
- edsl/utilities/utilities.py +9 -1
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/METADATA +3 -1
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/RECORD +75 -69
- edsl/jobs/interviews/retry_management.py +0 -39
- edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
- edsl/scenarios/ScenarioImageMixin.py +0 -100
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/WHEEL +0 -0
edsl/questions/QuestionBase.py
CHANGED
@@ -44,6 +44,13 @@ class QuestionBase(
|
|
44
44
|
_answering_instructions = None
|
45
45
|
_question_presentation = None
|
46
46
|
|
47
|
+
@property
|
48
|
+
def response_model(self) -> type["BaseModel"]:
|
49
|
+
if self._response_model is not None:
|
50
|
+
return self._response_model
|
51
|
+
else:
|
52
|
+
return self.create_response_model()
|
53
|
+
|
47
54
|
# region: Validation and simulation methods
|
48
55
|
@property
|
49
56
|
def response_validator(self) -> "ResponseValidatorBase":
|
@@ -99,20 +106,17 @@ class QuestionBase(
|
|
99
106
|
comment: Optional[str]
|
100
107
|
generated_tokens: Optional[str]
|
101
108
|
|
102
|
-
def _validate_answer(
|
109
|
+
def _validate_answer(
|
110
|
+
self, answer: dict, replacement_dict: dict = None
|
111
|
+
) -> ValidatedAnswer:
|
103
112
|
"""Validate the answer.
|
104
113
|
>>> from edsl.exceptions import QuestionAnswerValidationError
|
105
114
|
>>> from edsl import QuestionFreeText as Q
|
106
|
-
>>> Q.example()._validate_answer({'answer': 'Hello'})
|
107
|
-
{'answer': 'Hello', 'generated_tokens':
|
108
|
-
>>> Q.example()._validate_answer({'shmanswer': 1})
|
109
|
-
Traceback (most recent call last):
|
110
|
-
...
|
111
|
-
edsl.exceptions.questions.QuestionAnswerValidationError:...
|
112
|
-
...
|
115
|
+
>>> Q.example()._validate_answer({'answer': 'Hello', 'generated_tokens': 'Hello'})
|
116
|
+
{'answer': 'Hello', 'generated_tokens': 'Hello'}
|
113
117
|
"""
|
114
118
|
|
115
|
-
return self.response_validator.validate(answer)
|
119
|
+
return self.response_validator.validate(answer, replacement_dict)
|
116
120
|
|
117
121
|
# endregion
|
118
122
|
|
@@ -471,6 +475,7 @@ class QuestionBase(
|
|
471
475
|
self,
|
472
476
|
scenario: Optional[dict] = None,
|
473
477
|
agent: Optional[dict] = {},
|
478
|
+
answers: Optional[dict] = None,
|
474
479
|
include_question_name: bool = False,
|
475
480
|
height: Optional[int] = None,
|
476
481
|
width: Optional[int] = None,
|
@@ -482,6 +487,17 @@ class QuestionBase(
|
|
482
487
|
if scenario is None:
|
483
488
|
scenario = {}
|
484
489
|
|
490
|
+
prior_answers_dict = {}
|
491
|
+
|
492
|
+
if isinstance(answers, dict):
|
493
|
+
for key, value in answers.items():
|
494
|
+
if not key.endswith("_comment") and not key.endswith(
|
495
|
+
"_generated_tokens"
|
496
|
+
):
|
497
|
+
prior_answers_dict[key] = {"answer": value}
|
498
|
+
|
499
|
+
# breakpoint()
|
500
|
+
|
485
501
|
base_template = """
|
486
502
|
<div id="{{ question_name }}" class="survey_question" data-type="{{ question_type }}">
|
487
503
|
{% if include_question_name %}
|
@@ -501,13 +517,40 @@ class QuestionBase(
|
|
501
517
|
|
502
518
|
base_template = Template(base_template)
|
503
519
|
|
504
|
-
|
505
|
-
"
|
506
|
-
"
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
520
|
+
context = {
|
521
|
+
"scenario": scenario,
|
522
|
+
"agent": agent,
|
523
|
+
} | prior_answers_dict
|
524
|
+
|
525
|
+
# Render the question text
|
526
|
+
try:
|
527
|
+
question_text = Template(self.question_text).render(context)
|
528
|
+
except Exception as e:
|
529
|
+
print(
|
530
|
+
f"Error rendering question: question_text = {self.question_text}, error = {e}"
|
531
|
+
)
|
532
|
+
question_text = self.question_text
|
533
|
+
|
534
|
+
try:
|
535
|
+
question_content = Template(question_content).render(context)
|
536
|
+
except Exception as e:
|
537
|
+
print(
|
538
|
+
f"Error rendering question: question_content = {question_content}, error = {e}"
|
539
|
+
)
|
540
|
+
question_content = question_content
|
541
|
+
|
542
|
+
try:
|
543
|
+
params = {
|
544
|
+
"question_name": self.question_name,
|
545
|
+
"question_text": question_text,
|
546
|
+
"question_type": self.question_type,
|
547
|
+
"question_content": question_content,
|
548
|
+
"include_question_name": include_question_name,
|
549
|
+
}
|
550
|
+
except Exception as e:
|
551
|
+
raise ValueError(
|
552
|
+
f"Error rendering question: params = {params}, error = {e}"
|
553
|
+
)
|
511
554
|
rendered_html = base_template.render(**params)
|
512
555
|
|
513
556
|
if iframe:
|
@@ -526,6 +569,21 @@ class QuestionBase(
|
|
526
569
|
|
527
570
|
return rendered_html
|
528
571
|
|
572
|
+
@classmethod
|
573
|
+
def example_model(cls):
|
574
|
+
from edsl import Model
|
575
|
+
|
576
|
+
q = cls.example()
|
577
|
+
m = Model("test", canned_response=cls._simulate_answer(q)["answer"])
|
578
|
+
|
579
|
+
return m
|
580
|
+
|
581
|
+
@classmethod
|
582
|
+
def example_results(cls):
|
583
|
+
m = cls.example_model()
|
584
|
+
q = cls.example()
|
585
|
+
return q.by(m).run(cache=False)
|
586
|
+
|
529
587
|
def rich_print(self):
|
530
588
|
"""Print the question in a rich format."""
|
531
589
|
from rich.table import Table
|
@@ -95,6 +95,34 @@ class QuestionBaseGenMixin:
|
|
95
95
|
questions.append(QuestionBase.from_dict(new_data))
|
96
96
|
return questions
|
97
97
|
|
98
|
+
def render(self, replacement_dict: dict) -> "QuestionBase":
|
99
|
+
"""Render the question components as jinja2 templates with the replacement dictionary."""
|
100
|
+
from jinja2 import Environment
|
101
|
+
from edsl import Scenario
|
102
|
+
|
103
|
+
strings_only_replacement_dict = {
|
104
|
+
k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
|
105
|
+
}
|
106
|
+
|
107
|
+
def render_string(value: str) -> str:
|
108
|
+
if value is None or not isinstance(value, str):
|
109
|
+
return value
|
110
|
+
else:
|
111
|
+
try:
|
112
|
+
return (
|
113
|
+
Environment()
|
114
|
+
.from_string(value)
|
115
|
+
.render(strings_only_replacement_dict)
|
116
|
+
)
|
117
|
+
except Exception as e:
|
118
|
+
import warnings
|
119
|
+
|
120
|
+
warnings.warn("Failed to render string: " + value)
|
121
|
+
# breakpoint()
|
122
|
+
return value
|
123
|
+
|
124
|
+
return self.apply_function(render_string)
|
125
|
+
|
98
126
|
def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
|
99
127
|
"""Apply a function to the question parts
|
100
128
|
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -1,8 +1,60 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import
|
3
|
-
|
2
|
+
from typing import Any, Optional, Union, List
|
3
|
+
|
4
|
+
from pydantic import Field, BaseModel, validator
|
5
|
+
|
4
6
|
from edsl.questions.QuestionBase import QuestionBase
|
5
7
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
8
|
+
from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
|
9
|
+
|
10
|
+
|
11
|
+
class BudgewResponseValidator(ResponseValidatorABC):
|
12
|
+
valid_examples = []
|
13
|
+
|
14
|
+
invalid_examples = []
|
15
|
+
|
16
|
+
def fix(self, response, verbose=False):
|
17
|
+
if verbose:
|
18
|
+
print(f"Fixing list response: {response}")
|
19
|
+
answer = str(response.get("answer") or response.get("generated_tokens", ""))
|
20
|
+
if len(answer.split(",")) > 0:
|
21
|
+
return (
|
22
|
+
{"answer": answer.split(",")} | {"comment": response.get("comment")}
|
23
|
+
if "comment" in response
|
24
|
+
else {}
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def create_budget_model(
|
29
|
+
budget_sum: float, permissive: bool, question_options: List[str]
|
30
|
+
):
|
31
|
+
class BudgetResponse(BaseModel):
|
32
|
+
answer: List[float] = Field(
|
33
|
+
...,
|
34
|
+
description="List of non-negative numbers representing budget allocation",
|
35
|
+
min_items=len(question_options),
|
36
|
+
max_items=len(question_options),
|
37
|
+
)
|
38
|
+
comment: Optional[str] = None
|
39
|
+
generated_tokens: Optional[str] = None
|
40
|
+
|
41
|
+
@validator("answer")
|
42
|
+
def validate_answer(cls, v):
|
43
|
+
if len(v) != len(question_options):
|
44
|
+
raise ValueError(f"Must provide {len(question_options)} values")
|
45
|
+
if any(x < 0 for x in v):
|
46
|
+
raise ValueError("All values must be non-negative")
|
47
|
+
total = sum(v)
|
48
|
+
if not permissive and total != budget_sum:
|
49
|
+
raise ValueError(f"Sum of numbers must equal {budget_sum}")
|
50
|
+
elif permissive and total > budget_sum:
|
51
|
+
raise ValueError(f"Sum of numbers cannot exceed {budget_sum}")
|
52
|
+
return v
|
53
|
+
|
54
|
+
class Config:
|
55
|
+
extra = "forbid"
|
56
|
+
|
57
|
+
return BudgetResponse
|
6
58
|
|
7
59
|
|
8
60
|
class QuestionBudget(QuestionBase):
|
@@ -12,7 +64,7 @@ class QuestionBudget(QuestionBase):
|
|
12
64
|
budget_sum: int = IntegerDescriptor(none_allowed=False)
|
13
65
|
question_options: list[str] = QuestionOptionsDescriptor(q_budget=True)
|
14
66
|
_response_model = None
|
15
|
-
response_validator_class =
|
67
|
+
response_validator_class = BudgewResponseValidator
|
16
68
|
|
17
69
|
def __init__(
|
18
70
|
self,
|
@@ -20,8 +72,10 @@ class QuestionBudget(QuestionBase):
|
|
20
72
|
question_text: str,
|
21
73
|
question_options: list[str],
|
22
74
|
budget_sum: int,
|
75
|
+
include_comment: bool = True,
|
23
76
|
question_presentation: Optional[str] = None,
|
24
77
|
answering_instructions: Optional[str] = None,
|
78
|
+
permissive: bool = False,
|
25
79
|
):
|
26
80
|
"""Instantiate a new QuestionBudget.
|
27
81
|
|
@@ -36,20 +90,17 @@ class QuestionBudget(QuestionBase):
|
|
36
90
|
self.budget_sum = budget_sum
|
37
91
|
self.question_presentation = question_presentation
|
38
92
|
self.answering_instructions = answering_instructions
|
93
|
+
self.permissive = permissive
|
94
|
+
self.include_comment = include_comment
|
39
95
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"""Validate the answer."""
|
45
|
-
self._validate_answer_template_basic(answer)
|
46
|
-
self._validate_answer_key_value(answer, "answer", dict)
|
47
|
-
self._validate_answer_budget(answer)
|
48
|
-
return answer
|
96
|
+
def create_response_model(self):
|
97
|
+
return create_budget_model(
|
98
|
+
self.budget_sum, self.permissive, self.question_options
|
99
|
+
)
|
49
100
|
|
50
101
|
def _translate_answer_code_to_answer(
|
51
|
-
self,
|
52
|
-
):
|
102
|
+
self, answer_code, combined_dict
|
103
|
+
) -> list[dict]:
|
53
104
|
"""
|
54
105
|
Translate the answer codes to the actual answers.
|
55
106
|
|
@@ -58,35 +109,35 @@ class QuestionBudget(QuestionBase):
|
|
58
109
|
This code will translate that to "a".
|
59
110
|
"""
|
60
111
|
translated_codes = []
|
61
|
-
for answer_code,
|
62
|
-
translated_codes.append({
|
112
|
+
for answer_code, question_option in zip(answer_code, self.question_options):
|
113
|
+
translated_codes.append({question_option: answer_code})
|
63
114
|
|
64
115
|
return translated_codes
|
65
116
|
|
66
|
-
def _simulate_answer(self, human_readable=True):
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
117
|
+
# def _simulate_answer(self, human_readable=True):
|
118
|
+
# """Simulate a valid answer for debugging purposes (what the validator expects)."""
|
119
|
+
# from edsl.utilities.utilities import random_string
|
120
|
+
|
121
|
+
# if human_readable:
|
122
|
+
# keys = self.question_options
|
123
|
+
# else:
|
124
|
+
# keys = range(len(self.question_options))
|
125
|
+
# remaining_budget = self.budget_sum
|
126
|
+
# values = []
|
127
|
+
# for _ in range(len(self.question_options)):
|
128
|
+
# if _ == len(self.question_options) - 1:
|
129
|
+
# # Assign remaining budget to the last value
|
130
|
+
# values.append(remaining_budget)
|
131
|
+
# else:
|
132
|
+
# # Generate a random value between 0 and remaining budget
|
133
|
+
# value = random.randint(0, remaining_budget)
|
134
|
+
# values.append(value)
|
135
|
+
# remaining_budget -= value
|
136
|
+
# answer = dict(zip(keys, values))
|
137
|
+
# return {
|
138
|
+
# "answer": answer,
|
139
|
+
# "comment": random_string(),
|
140
|
+
# }
|
90
141
|
|
91
142
|
@property
|
92
143
|
def question_html_content(self) -> str:
|
@@ -133,13 +184,14 @@ class QuestionBudget(QuestionBase):
|
|
133
184
|
# Helpful methods
|
134
185
|
################
|
135
186
|
@classmethod
|
136
|
-
def example(cls) -> QuestionBudget:
|
187
|
+
def example(cls, include_comment: bool = True) -> QuestionBudget:
|
137
188
|
"""Return an example of a budget question."""
|
138
189
|
return cls(
|
139
190
|
question_name="food_budget",
|
140
191
|
question_text="How would you allocate $100?",
|
141
192
|
question_options=["Pizza", "Ice Cream", "Burgers", "Salad"],
|
142
193
|
budget_sum=100,
|
194
|
+
include_comment=include_comment,
|
143
195
|
)
|
144
196
|
|
145
197
|
|
@@ -245,7 +245,7 @@ class QuestionCheckBox(QuestionBase):
|
|
245
245
|
|
246
246
|
scenario = scenario or Scenario()
|
247
247
|
translated_options = [
|
248
|
-
Template(option).render(scenario) for option in self.question_options
|
248
|
+
Template(str(option)).render(scenario) for option in self.question_options
|
249
249
|
]
|
250
250
|
translated_codes = []
|
251
251
|
for answer_code in answer_codes:
|
@@ -36,6 +36,12 @@ class FreeTextResponseValidator(ResponseValidatorABC):
|
|
36
36
|
),
|
37
37
|
]
|
38
38
|
|
39
|
+
def fix(self, response, verbose=False):
|
40
|
+
return {
|
41
|
+
"answer": str(response.get("generated_tokens")),
|
42
|
+
"generated_tokens": str(response.get("generated_tokens")),
|
43
|
+
}
|
44
|
+
|
39
45
|
|
40
46
|
class QuestionFreeText(QuestionBase):
|
41
47
|
"""This question prompts the agent to respond with free text."""
|
@@ -1,22 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import Union, Literal, Optional
|
3
|
-
from jinja2 import Template
|
2
|
+
from typing import Union, Literal, Optional, List, Any
|
4
3
|
|
4
|
+
from jinja2 import Template
|
5
5
|
from pydantic import BaseModel, Field
|
6
|
-
from typing import Optional, Literal
|
7
6
|
|
7
|
+
from edsl.scenarios.Scenario import Scenario
|
8
8
|
from edsl.questions.QuestionBase import QuestionBase
|
9
9
|
from edsl.questions.descriptors import QuestionOptionsDescriptor
|
10
10
|
from edsl.questions.decorators import inject_exception
|
11
|
-
|
12
11
|
from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
|
13
|
-
from edsl.questions.ResponseValidatorABC import BaseResponse
|
14
|
-
|
15
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
16
|
-
|
17
|
-
from pydantic import BaseModel, Field, create_model
|
18
|
-
|
19
|
-
from typing import List, Any, Literal
|
20
12
|
|
21
13
|
|
22
14
|
def create_response_model(choices: List[str], permissive: bool = False):
|
@@ -27,7 +19,6 @@ def create_response_model(choices: List[str], permissive: bool = False):
|
|
27
19
|
:param permissive: If True, any value will be accepted as an answer.
|
28
20
|
:return: A new Pydantic model class.
|
29
21
|
"""
|
30
|
-
# Convert the choices list to a tuple for use with Literal
|
31
22
|
choice_tuple = tuple(choices)
|
32
23
|
|
33
24
|
if not permissive:
|
@@ -66,16 +57,6 @@ def create_response_model(choices: List[str], permissive: bool = False):
|
|
66
57
|
return ChoiceResponse
|
67
58
|
|
68
59
|
|
69
|
-
def fix_multiple_choice(response, question_options, use_code, verbose=False):
|
70
|
-
"""Fix the response to a multiple choice question.
|
71
|
-
Respnse is a dictionary with keys:
|
72
|
-
- answer: the answer code
|
73
|
-
- generated_tokens: the generated tokens
|
74
|
-
- comment: the comment
|
75
|
-
"""
|
76
|
-
pass
|
77
|
-
|
78
|
-
|
79
60
|
class MultipleChoiceResponseValidator(ResponseValidatorABC):
|
80
61
|
required_params = ["question_options", "use_code"]
|
81
62
|
|
@@ -161,6 +142,11 @@ class QuestionMultipleChoice(QuestionBase):
|
|
161
142
|
:param question_name: The name of the question.
|
162
143
|
:param question_text: The text of the question.
|
163
144
|
:param question_options: The options the agent should select from.
|
145
|
+
:param include_comment: Whether to include a comment field.
|
146
|
+
:param use_code: Whether to use code for the options.
|
147
|
+
:param answering_instructions: Instructions for the question.
|
148
|
+
:param question_presentation: The presentation of the question.
|
149
|
+
:param permissive: Whether to force the answer to be one of the options.
|
164
150
|
|
165
151
|
"""
|
166
152
|
self.question_name = question_name
|
@@ -177,7 +163,11 @@ class QuestionMultipleChoice(QuestionBase):
|
|
177
163
|
# Answer methods
|
178
164
|
################
|
179
165
|
|
180
|
-
def create_response_model(self):
|
166
|
+
def create_response_model(self, replacement_dict: dict = None):
|
167
|
+
if replacement_dict is None:
|
168
|
+
replacement_dict = {}
|
169
|
+
# The replacement dict that could be from scenario, current answers, etc. to populate the response model
|
170
|
+
|
181
171
|
if self.use_code:
|
182
172
|
return create_response_model(
|
183
173
|
list(range(len(self.question_options))), self.permissive
|
@@ -202,7 +192,6 @@ class QuestionMultipleChoice(QuestionBase):
|
|
202
192
|
'Happy'
|
203
193
|
|
204
194
|
"""
|
205
|
-
from edsl.scenarios.Scenario import Scenario
|
206
195
|
|
207
196
|
scenario = scenario or Scenario()
|
208
197
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
|
+
# from decimal import Decimal
|
3
4
|
from random import uniform
|
4
5
|
from typing import Any, Optional, Union, Literal
|
5
6
|
|
@@ -14,8 +15,8 @@ from edsl.exceptions.questions import QuestionAnswerValidationError
|
|
14
15
|
|
15
16
|
|
16
17
|
def create_numeric_response(
|
17
|
-
min_value: Optional[
|
18
|
-
max_value: Optional[
|
18
|
+
min_value: Optional[float] = None,
|
19
|
+
max_value: Optional[float] = None,
|
19
20
|
permissive=False,
|
20
21
|
):
|
21
22
|
field_kwargs = {}
|
@@ -27,7 +28,7 @@ def create_numeric_response(
|
|
27
28
|
field_kwargs["le"] = max_value
|
28
29
|
|
29
30
|
class ConstrainedNumericResponse(BaseModel):
|
30
|
-
answer: Union[
|
31
|
+
answer: Union[int, float] = Field(**field_kwargs)
|
31
32
|
comment: Optional[str] = Field(None)
|
32
33
|
generated_tokens: Optional[Any] = Field(None)
|
33
34
|
|
edsl/questions/Quick.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
from edsl import (
|
2
|
+
QuestionFreeText,
|
3
|
+
QuestionMultipleChoice,
|
4
|
+
Survey,
|
5
|
+
QuestionList,
|
6
|
+
Question,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
def Quick(question_text):
|
11
|
+
q_type = QuestionMultipleChoice(
|
12
|
+
question_text=f"A researcher is asking a language model this: {question_text}. What is the most appropriate type of question to ask?",
|
13
|
+
question_name="potential_question_type",
|
14
|
+
question_options=["multiple_choice", "list", "free_text"],
|
15
|
+
)
|
16
|
+
|
17
|
+
q_name = QuestionFreeText(
|
18
|
+
question_text=f"A researcher is asking a language model this: {question_text}. What is a good name for this question that's a valid python identifier? Just return the proposed identifer",
|
19
|
+
question_name="potential_question_name",
|
20
|
+
)
|
21
|
+
|
22
|
+
q_options = QuestionList(
|
23
|
+
question_text=f"A research is asking this question: { question_text }. What are the possible options for this question?",
|
24
|
+
question_name="potential_question_options",
|
25
|
+
)
|
26
|
+
|
27
|
+
survey = Survey([q_type, q_name, q_options]).add_skip_rule(
|
28
|
+
q_options, "{{ potential_question_type }} != 'multiple_choice'"
|
29
|
+
)
|
30
|
+
return survey
|
31
|
+
# results = survey.run()
|
32
|
+
# question_type = results.select("potential_question_type").first()
|
33
|
+
# question_options = results.select("potential_question_options").first()
|
34
|
+
# question_name = results.select("potential_question_name").first()
|
35
|
+
# print("Question Type: ", question_type)
|
36
|
+
# print("Question Name: ", question_name)
|
37
|
+
# print("Question Options: ", question_options)
|
38
|
+
# if question_options == None:
|
39
|
+
# return Question(question_type, question_name = question_name)
|
40
|
+
# else:
|
41
|
+
# return Question(question_type, question_name = question_name, question_options = question_options)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from pydantic import BaseModel, Field, field_validator
|
3
|
-
|
3
|
+
|
4
|
+
# from decimal import Decimal
|
4
5
|
from typing import Optional, Any, List, TypedDict
|
5
6
|
|
6
7
|
from edsl.exceptions import QuestionAnswerValidationError
|
@@ -64,7 +65,7 @@ class ResponseValidatorABC(ABC):
|
|
64
65
|
>>> rv = ResponseValidatorABC.example()
|
65
66
|
>>> rv.override_answer = {"answer": 42}
|
66
67
|
>>> rv.validate({"answer": 23})
|
67
|
-
{'answer':
|
68
|
+
{'answer': 42, 'comment': None, 'generated_tokens': None}
|
68
69
|
"""
|
69
70
|
if self.exception_to_throw:
|
70
71
|
raise self.exception_to_throw
|
@@ -75,7 +76,7 @@ class ResponseValidatorABC(ABC):
|
|
75
76
|
|
76
77
|
>>> rv = ResponseValidatorABC.example("numerical")
|
77
78
|
>>> rv._base_validate({"answer": 42})
|
78
|
-
ConstrainedNumericResponse(answer=
|
79
|
+
ConstrainedNumericResponse(answer=42, comment=None, generated_tokens=None)
|
79
80
|
"""
|
80
81
|
try:
|
81
82
|
return self.response_model(**data)
|
@@ -91,13 +92,17 @@ class ResponseValidatorABC(ABC):
|
|
91
92
|
generated_tokens: Optional[str]
|
92
93
|
|
93
94
|
def validate(
|
94
|
-
self,
|
95
|
+
self,
|
96
|
+
raw_edsl_answer_dict: RawEdslAnswerDict,
|
97
|
+
fix=False,
|
98
|
+
verbose=False,
|
99
|
+
replacement_dict: dict = None,
|
95
100
|
) -> EdslAnswerDict:
|
96
101
|
"""This is the main validation function.
|
97
102
|
|
98
103
|
>>> rv = ResponseValidatorABC.example("numerical")
|
99
104
|
>>> rv.validate({"answer": 42})
|
100
|
-
{'answer':
|
105
|
+
{'answer': 42, 'comment': None, 'generated_tokens': None}
|
101
106
|
>>> rv.max_value
|
102
107
|
86.7
|
103
108
|
>>> rv.validate({"answer": "120"})
|
@@ -109,7 +114,7 @@ class ResponseValidatorABC(ABC):
|
|
109
114
|
>>> q.permissive = True
|
110
115
|
>>> rv = q.response_validator
|
111
116
|
>>> rv.validate({"answer": "120"})
|
112
|
-
{'answer':
|
117
|
+
{'answer': 120, 'comment': None, 'generated_tokens': None}
|
113
118
|
>>> rv.validate({"answer": "poo"})
|
114
119
|
Traceback (most recent call last):
|
115
120
|
...
|
@@ -22,6 +22,7 @@ class QuestionLinearScale(QuestionMultipleChoice):
|
|
22
22
|
option_labels: Optional[dict[int, str]] = None,
|
23
23
|
answering_instructions: Optional[str] = None,
|
24
24
|
question_presentation: Optional[str] = None,
|
25
|
+
include_comment: Optional[bool] = True,
|
25
26
|
):
|
26
27
|
"""Instantiate a new QuestionLinearScale.
|
27
28
|
|
@@ -36,6 +37,7 @@ class QuestionLinearScale(QuestionMultipleChoice):
|
|
36
37
|
question_text=question_text,
|
37
38
|
question_options=question_options,
|
38
39
|
use_code=False, # question linear scale will have it's own code
|
40
|
+
include_comment=include_comment,
|
39
41
|
)
|
40
42
|
self.question_options = question_options
|
41
43
|
self.option_labels = (
|
@@ -49,13 +51,14 @@ class QuestionLinearScale(QuestionMultipleChoice):
|
|
49
51
|
################
|
50
52
|
@classmethod
|
51
53
|
@inject_exception
|
52
|
-
def example(cls) -> QuestionLinearScale:
|
54
|
+
def example(cls, include_comment: bool = True) -> QuestionLinearScale:
|
53
55
|
"""Return an example of a linear scale question."""
|
54
56
|
return cls(
|
55
57
|
question_text="How much do you like ice cream?",
|
56
58
|
question_options=[1, 2, 3, 4, 5],
|
57
59
|
question_name="ice_cream",
|
58
60
|
option_labels={1: "I hate it", 5: "I love it"},
|
61
|
+
include_comment=include_comment,
|
59
62
|
)
|
60
63
|
|
61
64
|
|
@@ -20,6 +20,7 @@ class QuestionTopK(QuestionCheckBox):
|
|
20
20
|
max_selections: int,
|
21
21
|
question_presentation: Optional[str] = None,
|
22
22
|
answering_instructions: Optional[str] = None,
|
23
|
+
include_comment: Optional[bool] = True,
|
23
24
|
):
|
24
25
|
"""Initialize the question.
|
25
26
|
|
@@ -37,6 +38,7 @@ class QuestionTopK(QuestionCheckBox):
|
|
37
38
|
max_selections=max_selections,
|
38
39
|
question_presentation=question_presentation,
|
39
40
|
answering_instructions=answering_instructions,
|
41
|
+
include_comment=include_comment,
|
40
42
|
)
|
41
43
|
if min_selections != max_selections:
|
42
44
|
raise QuestionCreationValidationError(
|
@@ -52,7 +54,7 @@ class QuestionTopK(QuestionCheckBox):
|
|
52
54
|
################
|
53
55
|
@classmethod
|
54
56
|
@inject_exception
|
55
|
-
def example(cls) -> QuestionTopK:
|
57
|
+
def example(cls, include_comment: bool = True) -> QuestionTopK:
|
56
58
|
"""Return an example question."""
|
57
59
|
return cls(
|
58
60
|
question_name="two_fruits",
|
@@ -60,6 +62,7 @@ class QuestionTopK(QuestionCheckBox):
|
|
60
62
|
question_options=["apple", "banana", "carrot", "durian"],
|
61
63
|
min_selections=2,
|
62
64
|
max_selections=2,
|
65
|
+
include_comment=include_comment,
|
63
66
|
)
|
64
67
|
|
65
68
|
|