edsl 0.1.32__py3-none-any.whl → 0.1.33__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 +9 -3
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +8 -3
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +40 -8
- edsl/agents/AgentList.py +43 -0
- edsl/agents/Invigilator.py +135 -219
- edsl/agents/InvigilatorBase.py +148 -59
- edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +138 -89
- edsl/agents/__init__.py +1 -0
- edsl/auto/AutoStudy.py +117 -0
- edsl/auto/StageBase.py +230 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +73 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +224 -0
- edsl/config.py +47 -56
- edsl/coop/PriceFetcher.py +58 -0
- edsl/coop/coop.py +50 -7
- edsl/data/Cache.py +35 -1
- edsl/data_transfer_models.py +73 -38
- edsl/enums.py +4 -0
- edsl/exceptions/language_models.py +25 -1
- edsl/exceptions/questions.py +62 -5
- edsl/exceptions/results.py +4 -0
- edsl/inference_services/AnthropicService.py +13 -11
- edsl/inference_services/AwsBedrock.py +19 -17
- edsl/inference_services/AzureAI.py +37 -20
- edsl/inference_services/GoogleService.py +16 -12
- edsl/inference_services/GroqService.py +2 -0
- edsl/inference_services/InferenceServiceABC.py +58 -3
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OpenAIService.py +48 -54
- edsl/inference_services/TestService.py +80 -0
- edsl/inference_services/TogetherAIService.py +170 -0
- edsl/inference_services/models_available_cache.py +0 -6
- edsl/inference_services/registry.py +6 -0
- edsl/jobs/Answers.py +10 -12
- edsl/jobs/FailedQuestion.py +78 -0
- edsl/jobs/Jobs.py +37 -22
- edsl/jobs/buckets/BucketCollection.py +24 -15
- edsl/jobs/buckets/TokenBucket.py +93 -14
- edsl/jobs/interviews/Interview.py +366 -78
- edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +14 -68
- edsl/jobs/interviews/InterviewExceptionEntry.py +85 -19
- edsl/jobs/runners/JobsRunnerAsyncio.py +146 -175
- edsl/jobs/runners/JobsRunnerStatus.py +331 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
- edsl/jobs/tasks/TaskHistory.py +148 -213
- edsl/language_models/LanguageModel.py +261 -156
- edsl/language_models/ModelList.py +2 -2
- edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/registry.py +23 -6
- edsl/language_models/repair.py +0 -19
- edsl/language_models/utilities.py +61 -0
- edsl/notebooks/Notebook.py +20 -2
- edsl/prompts/Prompt.py +52 -2
- edsl/questions/AnswerValidatorMixin.py +23 -26
- edsl/questions/QuestionBase.py +330 -249
- edsl/questions/QuestionBaseGenMixin.py +133 -0
- edsl/questions/QuestionBasePromptsMixin.py +266 -0
- edsl/questions/QuestionBudget.py +99 -41
- edsl/questions/QuestionCheckBox.py +227 -35
- edsl/questions/QuestionExtract.py +98 -27
- edsl/questions/QuestionFreeText.py +52 -29
- edsl/questions/QuestionFunctional.py +7 -0
- edsl/questions/QuestionList.py +141 -22
- edsl/questions/QuestionMultipleChoice.py +159 -65
- edsl/questions/QuestionNumerical.py +88 -46
- edsl/questions/QuestionRank.py +182 -24
- edsl/questions/Quick.py +41 -0
- edsl/questions/RegisterQuestionsMeta.py +31 -12
- edsl/questions/ResponseValidatorABC.py +170 -0
- edsl/questions/__init__.py +3 -4
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +10 -5
- edsl/questions/derived/QuestionLinearScale.py +15 -2
- edsl/questions/derived/QuestionTopK.py +10 -1
- edsl/questions/derived/QuestionYesNo.py +24 -3
- edsl/questions/descriptors.py +43 -7
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_registry.py +6 -2
- edsl/questions/templates/__init__.py +0 -0
- 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/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/Dataset.py +20 -0
- edsl/results/DatasetExportMixin.py +46 -48
- edsl/results/DatasetTree.py +145 -0
- edsl/results/Result.py +32 -5
- edsl/results/Results.py +135 -46
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/results/Selector.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/FileStore.py +71 -10
- edsl/scenarios/Scenario.py +96 -25
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +361 -39
- edsl/scenarios/ScenarioListExportMixin.py +9 -0
- edsl/scenarios/ScenarioListPdfMixin.py +150 -4
- edsl/study/SnapShot.py +8 -1
- edsl/study/Study.py +32 -0
- edsl/surveys/Rule.py +10 -1
- edsl/surveys/RuleCollection.py +21 -5
- edsl/surveys/Survey.py +637 -311
- edsl/surveys/SurveyExportMixin.py +71 -9
- edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
- edsl/surveys/SurveyQualtricsImport.py +75 -4
- edsl/surveys/instructions/ChangeInstruction.py +47 -0
- edsl/surveys/instructions/Instruction.py +34 -0
- edsl/surveys/instructions/InstructionCollection.py +77 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +116 -0
- edsl/templates/error_reporting/interviews.html +10 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- edsl/utilities/utilities.py +9 -1
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/METADATA +5 -2
- edsl-0.1.33.dist-info/RECORD +295 -0
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
- edsl/jobs/interviews/retry_management.py +0 -37
- edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
- edsl/utilities/gcp_bucket/simple_example.py +0 -9
- edsl-0.1.32.dist-info/RECORD +0 -209
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
- {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/WHEEL +0 -0
@@ -1,26 +1,98 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
|
+
# from decimal import Decimal
|
3
4
|
from random import uniform
|
4
|
-
from typing import Any, Optional, Union
|
5
|
+
from typing import Any, Optional, Union, Literal
|
6
|
+
|
7
|
+
from pydantic import BaseModel, Field, field_validator
|
5
8
|
|
6
9
|
from edsl.exceptions import QuestionAnswerValidationError
|
7
10
|
from edsl.questions.QuestionBase import QuestionBase
|
8
11
|
from edsl.questions.descriptors import NumericalOrNoneDescriptor
|
12
|
+
from edsl.questions.decorators import inject_exception
|
13
|
+
from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
|
14
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
15
|
+
|
16
|
+
|
17
|
+
def create_numeric_response(
|
18
|
+
min_value: Optional[float] = None,
|
19
|
+
max_value: Optional[float] = None,
|
20
|
+
permissive=False,
|
21
|
+
):
|
22
|
+
field_kwargs = {}
|
23
|
+
if not permissive:
|
24
|
+
field_kwargs = {}
|
25
|
+
if min_value is not None:
|
26
|
+
field_kwargs["ge"] = min_value
|
27
|
+
if max_value is not None:
|
28
|
+
field_kwargs["le"] = max_value
|
29
|
+
|
30
|
+
class ConstrainedNumericResponse(BaseModel):
|
31
|
+
answer: Union[int, float] = Field(**field_kwargs)
|
32
|
+
comment: Optional[str] = Field(None)
|
33
|
+
generated_tokens: Optional[Any] = Field(None)
|
34
|
+
|
35
|
+
return ConstrainedNumericResponse
|
36
|
+
|
37
|
+
|
38
|
+
class NumericalResponseValidator(ResponseValidatorABC):
|
39
|
+
required_params = ["min_value", "max_value", "permissive"]
|
40
|
+
|
41
|
+
valid_examples = [
|
42
|
+
({"answer": 1}, {"min_value": 0, "max_value": 10}),
|
43
|
+
({"answer": 1}, {"min_value": None, "max_value": None}),
|
44
|
+
]
|
45
|
+
|
46
|
+
invalid_examples = [
|
47
|
+
({"answer": 10}, {"min_value": 0, "max_value": 5}, "Answer is out of range"),
|
48
|
+
({"answer": "ten"}, {"min_value": 0, "max_value": 5}, "Answer is not a number"),
|
49
|
+
({}, {"min_value": 0, "max_value": 5}, "Answer key is missing"),
|
50
|
+
]
|
51
|
+
|
52
|
+
def fix(self, response, verbose=False):
|
53
|
+
response_text = str(response).lower()
|
54
|
+
import re
|
55
|
+
|
56
|
+
if verbose:
|
57
|
+
print(f"Ivalid generated tokens was was: {response_text}")
|
58
|
+
pattern = r"\b\d+(?:\.\d+)?\b"
|
59
|
+
match = re.search(pattern, response_text.replace(",", ""))
|
60
|
+
solution = match.group(0) if match else response.get("answer")
|
61
|
+
if verbose:
|
62
|
+
print("Proposed solution is: ", solution)
|
63
|
+
if "comment" in response:
|
64
|
+
return {"answer": solution, "comment": response["comment"]}
|
65
|
+
else:
|
66
|
+
return {"answer": solution}
|
67
|
+
|
68
|
+
def _check_constraints(self, pydantic_edsl_answer: BaseModel):
|
69
|
+
pass
|
9
70
|
|
10
71
|
|
11
72
|
class QuestionNumerical(QuestionBase):
|
12
|
-
"""This question prompts the agent to answer with a numerical value.
|
73
|
+
"""This question prompts the agent to answer with a numerical value.
|
74
|
+
|
75
|
+
>>> QuestionNumerical.self_check()
|
76
|
+
|
77
|
+
"""
|
13
78
|
|
14
79
|
question_type = "numerical"
|
15
80
|
min_value: Optional[float] = NumericalOrNoneDescriptor()
|
16
81
|
max_value: Optional[float] = NumericalOrNoneDescriptor()
|
17
82
|
|
83
|
+
_response_model = None
|
84
|
+
response_validator_class = NumericalResponseValidator
|
85
|
+
|
18
86
|
def __init__(
|
19
87
|
self,
|
20
88
|
question_name: str,
|
21
89
|
question_text: str,
|
22
90
|
min_value: Optional[Union[int, float]] = None,
|
23
91
|
max_value: Optional[Union[int, float]] = None,
|
92
|
+
include_comment: bool = True,
|
93
|
+
question_presentation: Optional[str] = None,
|
94
|
+
answering_instructions: Optional[str] = None,
|
95
|
+
permissive: bool = False,
|
24
96
|
):
|
25
97
|
"""Initialize the question.
|
26
98
|
|
@@ -34,30 +106,17 @@ class QuestionNumerical(QuestionBase):
|
|
34
106
|
self.min_value = min_value
|
35
107
|
self.max_value = max_value
|
36
108
|
|
109
|
+
self.include_comment = include_comment
|
110
|
+
self.question_presentation = question_presentation
|
111
|
+
self.answering_instructions = answering_instructions
|
112
|
+
self.permissive = permissive
|
113
|
+
|
114
|
+
def create_response_model(self):
|
115
|
+
return create_numeric_response(self.min_value, self.max_value, self.permissive)
|
116
|
+
|
37
117
|
################
|
38
118
|
# Answer methods
|
39
119
|
################
|
40
|
-
def _validate_answer(
|
41
|
-
self, answer: dict[str, Any]
|
42
|
-
) -> dict[str, Union[str, float, int]]:
|
43
|
-
"""Validate the answer."""
|
44
|
-
self._validate_answer_template_basic(answer)
|
45
|
-
self._validate_answer_key_value_numeric(answer, "answer")
|
46
|
-
self._validate_answer_numerical(answer)
|
47
|
-
return answer
|
48
|
-
|
49
|
-
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
50
|
-
"""There is no answer code."""
|
51
|
-
return answer
|
52
|
-
|
53
|
-
def _simulate_answer(self, human_readable: bool = True):
|
54
|
-
"""Simulate a valid answer for debugging purposes."""
|
55
|
-
from edsl.utilities.utilities import random_string
|
56
|
-
|
57
|
-
return {
|
58
|
-
"answer": uniform(self.min_value, self.max_value),
|
59
|
-
"comment": random_string(),
|
60
|
-
}
|
61
120
|
|
62
121
|
@property
|
63
122
|
def question_html_content(self) -> str:
|
@@ -76,36 +135,19 @@ class QuestionNumerical(QuestionBase):
|
|
76
135
|
# Helpful methods
|
77
136
|
################
|
78
137
|
@classmethod
|
79
|
-
|
138
|
+
@inject_exception
|
139
|
+
def example(cls, include_comment=False) -> QuestionNumerical:
|
80
140
|
"""Return an example question."""
|
81
141
|
return cls(
|
82
142
|
question_name="age",
|
83
|
-
question_text="How old are you in years?",
|
143
|
+
question_text="You are a 45 year old man. How old are you in years?",
|
84
144
|
min_value=0,
|
85
145
|
max_value=86.7,
|
146
|
+
include_comment=include_comment,
|
86
147
|
)
|
87
148
|
|
88
149
|
|
89
|
-
|
90
|
-
"""Show example usage."""
|
91
|
-
from edsl.questions.QuestionNumerical import QuestionNumerical
|
92
|
-
|
93
|
-
q = QuestionNumerical.example()
|
94
|
-
q.question_text
|
95
|
-
q.min_value
|
96
|
-
q.max_value
|
97
|
-
# validate an answer
|
98
|
-
q._validate_answer({"answer": 1, "comment": "I like custard"})
|
99
|
-
# translate answer code
|
100
|
-
q._translate_answer_code_to_answer(1)
|
101
|
-
# simulate answer
|
102
|
-
q._simulate_answer()
|
103
|
-
q._simulate_answer(human_readable=False)
|
104
|
-
q._validate_answer(q._simulate_answer(human_readable=False))
|
105
|
-
# serialization (inherits from Question)
|
106
|
-
q.to_dict()
|
107
|
-
assert q.from_dict(q.to_dict()) == q
|
108
|
-
|
150
|
+
if __name__ == "__main__":
|
109
151
|
import doctest
|
110
152
|
|
111
153
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/questions/QuestionRank.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import random
|
3
|
+
import textwrap
|
3
4
|
from jinja2 import Template
|
4
5
|
from typing import Any, Optional, Union
|
5
6
|
from edsl.questions.QuestionBase import QuestionBase
|
@@ -10,6 +11,129 @@ from edsl.questions.descriptors import (
|
|
10
11
|
NumSelectionsDescriptor,
|
11
12
|
)
|
12
13
|
|
14
|
+
from edsl.prompts import Prompt
|
15
|
+
|
16
|
+
from pydantic import field_validator
|
17
|
+
from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
|
18
|
+
from edsl.questions.ResponseValidatorABC import BaseResponse
|
19
|
+
from edsl.exceptions import QuestionAnswerValidationError
|
20
|
+
|
21
|
+
from pydantic import BaseModel, Field, create_model
|
22
|
+
from typing import Optional, Any, List, Annotated, Literal
|
23
|
+
|
24
|
+
|
25
|
+
def create_response_model(
|
26
|
+
choices: list,
|
27
|
+
num_selections: Optional[int] = None,
|
28
|
+
permissive: bool = False,
|
29
|
+
):
|
30
|
+
"""
|
31
|
+
:param choices: A list of allowed values for the answer field.
|
32
|
+
:param include_comment: Whether to include a comment field in the model.
|
33
|
+
:return: A new Pydantic model class.
|
34
|
+
"""
|
35
|
+
# Convert the choices list to a tuple for use with Literal
|
36
|
+
choice_tuple = tuple(choices)
|
37
|
+
|
38
|
+
field_params = {}
|
39
|
+
if num_selections is not None and not permissive:
|
40
|
+
field_params["min_items"] = num_selections
|
41
|
+
field_params["max_items"] = num_selections
|
42
|
+
|
43
|
+
class RankResponse(BaseModel):
|
44
|
+
answer: Annotated[
|
45
|
+
List[Literal[choice_tuple]],
|
46
|
+
Field(..., **field_params),
|
47
|
+
] = Field(..., description="List of selected choices")
|
48
|
+
comment: Optional[str] = Field(None, description="Optional comment field")
|
49
|
+
generated_tokens: Optional[Any] = Field(None)
|
50
|
+
|
51
|
+
class Config:
|
52
|
+
@staticmethod
|
53
|
+
def json_schema_extra(schema: dict, model: BaseModel) -> None:
|
54
|
+
# Add the list of choices to the schema for better documentation
|
55
|
+
for prop in schema.get("properties", {}).values():
|
56
|
+
if prop.get("title") == "answer":
|
57
|
+
prop["items"] = {"enum": choices}
|
58
|
+
|
59
|
+
return RankResponse
|
60
|
+
|
61
|
+
|
62
|
+
class RankResponseValidator(ResponseValidatorABC):
|
63
|
+
required_params = ["num_selections", "permissive", "use_code", "question_options"]
|
64
|
+
valid_examples = []
|
65
|
+
invalid_examples = []
|
66
|
+
|
67
|
+
def fix(self, response, verbose=False):
|
68
|
+
if verbose:
|
69
|
+
print("Invalid response of QuestionRank was: ", False)
|
70
|
+
response_text = response.get("generated_tokens")
|
71
|
+
if response_text is None or response_text == "": # nothing to be done
|
72
|
+
return response
|
73
|
+
# Maybe it's a comma separated list?
|
74
|
+
response_text = str(response.get("answer"))
|
75
|
+
proposed_list = (
|
76
|
+
response_text.replace("[", "").replace("]", "").replace("'", "").split(",")
|
77
|
+
)
|
78
|
+
proposed_list = [item.strip() for item in proposed_list]
|
79
|
+
|
80
|
+
if verbose:
|
81
|
+
print("Using code? ", self.use_code)
|
82
|
+
if self.use_code:
|
83
|
+
try:
|
84
|
+
proposed_list = [int(i) for i in proposed_list]
|
85
|
+
except ValueError:
|
86
|
+
# print("Could not convert to int")
|
87
|
+
pass
|
88
|
+
|
89
|
+
if verbose:
|
90
|
+
print("Proposed solution is: ", proposed_list)
|
91
|
+
|
92
|
+
# print(f"Ivalid generated tokens was was: {response_text}")
|
93
|
+
if "comment" in response:
|
94
|
+
proposed_data = {
|
95
|
+
"answer": proposed_list,
|
96
|
+
"comment": response["comment"],
|
97
|
+
"generated_tokens": response.get("generated_tokens", None),
|
98
|
+
}
|
99
|
+
else:
|
100
|
+
proposed_data = {
|
101
|
+
"answer": proposed_list,
|
102
|
+
"generated_tokens": response.get("generated_tokens", None),
|
103
|
+
}
|
104
|
+
|
105
|
+
try:
|
106
|
+
self.response_model(**proposed_data)
|
107
|
+
return proposed_data
|
108
|
+
except Exception as e:
|
109
|
+
if verbose:
|
110
|
+
print(f"Proposed solution {proposed_data} is invalid. Error: {e}")
|
111
|
+
# return response
|
112
|
+
if verbose:
|
113
|
+
print("Now seeing if responses show up in the answer")
|
114
|
+
matches = []
|
115
|
+
for index, option in enumerate(self.question_options):
|
116
|
+
if self.use_code:
|
117
|
+
if str(index) in response_text:
|
118
|
+
if index not in matches:
|
119
|
+
matches.append(index)
|
120
|
+
else:
|
121
|
+
if option in response_text:
|
122
|
+
if option not in matches:
|
123
|
+
matches.append(option)
|
124
|
+
proposed_data = {
|
125
|
+
"answer": matches,
|
126
|
+
"comment": response.get("comment", None),
|
127
|
+
"generated_tokens": response.get("generated_tokens", None),
|
128
|
+
}
|
129
|
+
try:
|
130
|
+
self.response_model(**proposed_data)
|
131
|
+
return proposed_data
|
132
|
+
except Exception as e:
|
133
|
+
if verbose:
|
134
|
+
print(f"Proposed solution {proposed_data} is invalid. Error: {e}")
|
135
|
+
return response
|
136
|
+
|
13
137
|
|
14
138
|
class QuestionRank(QuestionBase):
|
15
139
|
"""This question prompts the agent to rank options from a list."""
|
@@ -18,12 +142,20 @@ class QuestionRank(QuestionBase):
|
|
18
142
|
question_options: list[str] = QuestionOptionsDescriptor()
|
19
143
|
num_selections = NumSelectionsDescriptor()
|
20
144
|
|
145
|
+
_response_model = None
|
146
|
+
response_validator_class = RankResponseValidator
|
147
|
+
|
21
148
|
def __init__(
|
22
149
|
self,
|
23
150
|
question_name: str,
|
24
151
|
question_text: str,
|
25
152
|
question_options: list[str],
|
26
153
|
num_selections: Optional[int] = None,
|
154
|
+
question_presentation: Optional[str] = None,
|
155
|
+
answering_instructions: Optional[str] = None,
|
156
|
+
permissive: bool = False,
|
157
|
+
use_code: bool = True,
|
158
|
+
include_comment: bool = True,
|
27
159
|
):
|
28
160
|
"""Initialize the question.
|
29
161
|
|
@@ -37,16 +169,33 @@ class QuestionRank(QuestionBase):
|
|
37
169
|
self.question_text = question_text
|
38
170
|
self.question_options = question_options
|
39
171
|
self.num_selections = num_selections or len(question_options)
|
172
|
+
self.question_presentation = question_presentation
|
173
|
+
self.answering_instructions = answering_instructions
|
174
|
+
self.permissive = permissive
|
175
|
+
self.use_code = use_code
|
176
|
+
self.include_comment = include_comment
|
177
|
+
|
178
|
+
def create_response_model(self):
|
179
|
+
choices = (
|
180
|
+
self.question_options
|
181
|
+
if not self.use_code
|
182
|
+
else range(len(self.question_options))
|
183
|
+
)
|
184
|
+
return create_response_model(
|
185
|
+
choices=choices,
|
186
|
+
num_selections=self.num_selections,
|
187
|
+
permissive=self.permissive,
|
188
|
+
)
|
40
189
|
|
41
190
|
################
|
42
191
|
# Answer methods
|
43
192
|
################
|
44
|
-
def _validate_answer(self, answer: Any) -> dict[str, list[int]]:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
193
|
+
# def _validate_answer(self, answer: Any) -> dict[str, list[int]]:
|
194
|
+
# """Validate the answer."""
|
195
|
+
# self._validate_answer_template_basic(answer)
|
196
|
+
# self._validate_answer_key_value(answer, "answer", list)
|
197
|
+
# self._validate_answer_rank(answer)
|
198
|
+
# return answer
|
50
199
|
|
51
200
|
def _translate_answer_code_to_answer(
|
52
201
|
self, answer_codes, scenario: Scenario = None
|
@@ -60,24 +209,27 @@ class QuestionRank(QuestionBase):
|
|
60
209
|
]
|
61
210
|
translated_codes = []
|
62
211
|
for answer_code in answer_codes:
|
63
|
-
|
212
|
+
if self._use_code:
|
213
|
+
translated_codes.append(translated_options[int(answer_code)])
|
214
|
+
else:
|
215
|
+
translated_codes.append(answer_code)
|
64
216
|
return translated_codes
|
65
217
|
|
66
|
-
def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
67
|
-
|
68
|
-
|
218
|
+
# def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
219
|
+
# """Simulate a valid answer for debugging purposes."""
|
220
|
+
# from edsl.utilities.utilities import random_string
|
69
221
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
222
|
+
# if human_readable:
|
223
|
+
# selected = random.sample(self.question_options, self.num_selections)
|
224
|
+
# else:
|
225
|
+
# selected = random.sample(
|
226
|
+
# range(len(self.question_options)), self.num_selections
|
227
|
+
# )
|
228
|
+
# answer = {
|
229
|
+
# "answer": selected,
|
230
|
+
# "comment": random_string(),
|
231
|
+
# }
|
232
|
+
# return answer
|
81
233
|
|
82
234
|
@property
|
83
235
|
def question_html_content(self) -> str:
|
@@ -129,13 +281,15 @@ class QuestionRank(QuestionBase):
|
|
129
281
|
# Helpful methods
|
130
282
|
################
|
131
283
|
@classmethod
|
132
|
-
def example(cls) -> QuestionRank:
|
284
|
+
def example(cls, use_code=False, include_comment=True) -> QuestionRank:
|
133
285
|
"""Return an example question."""
|
134
286
|
return cls(
|
135
287
|
question_name="rank_foods",
|
136
288
|
question_text="Rank your favorite foods.",
|
137
289
|
question_options=["Pizza", "Pasta", "Salad", "Soup"],
|
138
290
|
num_selections=2,
|
291
|
+
use_code=use_code,
|
292
|
+
include_comment=include_comment,
|
139
293
|
)
|
140
294
|
|
141
295
|
|
@@ -143,7 +297,7 @@ def main():
|
|
143
297
|
"""Show example usage."""
|
144
298
|
from edsl.questions.QuestionRank import QuestionRank
|
145
299
|
|
146
|
-
q = QuestionRank.example()
|
300
|
+
q = QuestionRank.example(use_code=True)
|
147
301
|
q.question_text
|
148
302
|
q.question_name
|
149
303
|
q.question_options
|
@@ -152,7 +306,7 @@ def main():
|
|
152
306
|
answer = {"answer": [0, 1], "comment": "I like pizza and pasta."}
|
153
307
|
q._validate_answer(answer)
|
154
308
|
# translate an answer code to an answer
|
155
|
-
q._translate_answer_code_to_answer([0, 1])
|
309
|
+
# q._translate_answer_code_to_answer([0, 1])
|
156
310
|
# simulate answer
|
157
311
|
q._simulate_answer()
|
158
312
|
q._simulate_answer(human_readable=False)
|
@@ -161,6 +315,10 @@ def main():
|
|
161
315
|
q.to_dict()
|
162
316
|
assert q.from_dict(q.to_dict()) == q
|
163
317
|
|
318
|
+
q = QuestionRank.example(use_code=False)
|
319
|
+
answer = {"answer": ["Pizza", "Pasta"], "comment": "I like pizza and pasta."}
|
320
|
+
q._validate_answer(answer)
|
321
|
+
|
164
322
|
import doctest
|
165
323
|
|
166
324
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
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)
|
@@ -4,6 +4,8 @@ from abc import ABCMeta
|
|
4
4
|
from edsl.enums import QuestionType
|
5
5
|
from edsl.exceptions.questions import QuestionMissingTypeError, QuestionBadTypeError
|
6
6
|
|
7
|
+
import inspect
|
8
|
+
|
7
9
|
|
8
10
|
class RegisterQuestionsMeta(ABCMeta):
|
9
11
|
"""Metaclass to register output elements in a registry i.e., those that have a parent."""
|
@@ -13,22 +15,39 @@ class RegisterQuestionsMeta(ABCMeta):
|
|
13
15
|
def __init__(cls, name, bases, dct):
|
14
16
|
"""Initialize the class and adds it to the registry if it's not the base class."""
|
15
17
|
super(RegisterQuestionsMeta, cls).__init__(name, bases, dct)
|
16
|
-
if
|
18
|
+
if (
|
19
|
+
name != "QuestionBase"
|
20
|
+
and name != "QuestionFunctional"
|
21
|
+
and name != "QuestionAddTwoNumbers"
|
22
|
+
):
|
17
23
|
## Enforce that all questions have a question_type class attribute
|
18
24
|
## and it comes from our enum of valid question types.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
required_attributes = [
|
26
|
+
"question_type",
|
27
|
+
"_response_model",
|
28
|
+
"response_validator_class",
|
29
|
+
]
|
30
|
+
for attr in required_attributes:
|
31
|
+
if not hasattr(cls, attr):
|
32
|
+
raise QuestionMissingTypeError(
|
33
|
+
f"Question must have a {attr} class attribute"
|
34
|
+
)
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
36
|
+
init_signature = inspect.signature(cls.__init__)
|
37
|
+
init_params = [p for p in init_signature.parameters if p != "self"]
|
38
|
+
required_params = [
|
39
|
+
"question_presentation",
|
40
|
+
"answering_instructions",
|
41
|
+
"question_name",
|
42
|
+
"question_text",
|
43
|
+
]
|
44
|
+
for param in required_params:
|
45
|
+
if param not in init_params:
|
46
|
+
raise QuestionBadTypeError(
|
47
|
+
f"Question type {name} must have a question_presentation parameter in its __init__ method"
|
48
|
+
)
|
31
49
|
|
50
|
+
if name != "QuestionBase":
|
32
51
|
RegisterQuestionsMeta._registry[name] = cls
|
33
52
|
|
34
53
|
@classmethod
|