edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 +197 -116
- edsl/__init__.py +15 -7
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +351 -147
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +101 -50
- edsl/agents/InvigilatorBase.py +62 -70
- edsl/agents/PromptConstructor.py +143 -225
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +18 -5
- edsl/auto/StageBase.py +53 -40
- edsl/auto/StageQuestions.py +2 -1
- edsl/auto/utilities.py +0 -6
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +125 -47
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +45 -27
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +31 -12
- edsl/data/RemoteCacheSync.py +154 -46
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +27 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +38 -19
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +7 -12
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +120 -79
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +47 -35
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -10
- edsl/inference_services/TogetherAIService.py +5 -3
- edsl/inference_services/data_structures.py +134 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +356 -431
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +6 -4
- edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +143 -408
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
- edsl/jobs/runners/JobsRunnerStatus.py +133 -165
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +38 -18
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +194 -236
- edsl/language_models/ModelList.py +28 -19
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +1 -2
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +68 -214
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionBudget.py +1 -1
- edsl/questions/QuestionCheckBox.py +3 -3
- edsl/questions/QuestionExtract.py +5 -7
- edsl/questions/QuestionFreeText.py +2 -3
- edsl/questions/QuestionList.py +10 -18
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +67 -23
- edsl/questions/QuestionNumerical.py +2 -4
- edsl/questions/QuestionRank.py +7 -17
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +2 -1
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
- edsl/questions/data_structures.py +20 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
- edsl/questions/question_registry.py +1 -1
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +168 -305
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +298 -206
- edsl/results/Results.py +149 -131
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/{Selector.py → results_selector.py} +23 -13
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +150 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioList.py +415 -244
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +270 -791
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
- edsl-0.1.39.dist-info/RECORD +358 -0
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.38.dev4.dist-info/RECORD +0 -277
- /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
- /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
- /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
- {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
edsl/questions/QuestionBase.py
CHANGED
@@ -2,31 +2,35 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
from abc import ABC, abstractmethod
|
5
|
-
from typing import Any, Type, Optional, List, Callable, Union, TypedDict
|
6
|
-
import copy
|
5
|
+
from typing import Any, Type, Optional, List, Callable, Union, TypedDict, TYPE_CHECKING
|
7
6
|
|
8
|
-
from edsl.exceptions import (
|
9
|
-
QuestionResponseValidationError,
|
10
|
-
QuestionAnswerValidationError,
|
7
|
+
from edsl.exceptions.questions import (
|
11
8
|
QuestionSerializationError,
|
12
9
|
)
|
13
10
|
from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
14
11
|
|
15
|
-
|
16
|
-
from edsl.questions.
|
17
|
-
from edsl.
|
18
|
-
from edsl.Base import PersistenceMixin, RichPrintingMixin
|
12
|
+
from edsl.questions.answer_validator_mixin import AnswerValidatorMixin
|
13
|
+
from edsl.questions.register_questions_meta import RegisterQuestionsMeta
|
14
|
+
from edsl.Base import PersistenceMixin, RepresentationMixin
|
19
15
|
from edsl.BaseDiff import BaseDiff, BaseDiffCollection
|
20
16
|
|
21
17
|
from edsl.questions.SimpleAskMixin import SimpleAskMixin
|
22
18
|
from edsl.questions.QuestionBasePromptsMixin import QuestionBasePromptsMixin
|
23
|
-
from edsl.questions.
|
24
|
-
from edsl.utilities.
|
19
|
+
from edsl.questions.question_base_gen_mixin import QuestionBaseGenMixin
|
20
|
+
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
24
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
25
|
+
from edsl.results.Results import Results
|
26
|
+
from edsl.agents.Agent import Agent
|
27
|
+
from edsl.surveys.Survey import Survey
|
28
|
+
from edsl.jobs.Jobs import Jobs
|
25
29
|
|
26
30
|
|
27
31
|
class QuestionBase(
|
28
32
|
PersistenceMixin,
|
29
|
-
|
33
|
+
RepresentationMixin,
|
30
34
|
SimpleAskMixin,
|
31
35
|
QuestionBasePromptsMixin,
|
32
36
|
QuestionBaseGenMixin,
|
@@ -36,6 +40,14 @@ class QuestionBase(
|
|
36
40
|
):
|
37
41
|
"""ABC for the Question class. All questions inherit from this class.
|
38
42
|
Some of the constraints on child questions are defined in the RegisterQuestionsMeta metaclass.
|
43
|
+
|
44
|
+
|
45
|
+
Every child class wiill have class attributes of question_type, _response_model and response_validator_class e.g.,
|
46
|
+
|
47
|
+
question_type = "free_text"
|
48
|
+
_response_model = FreeTextResponse
|
49
|
+
response_validator_class = FreeTextResponseValidator
|
50
|
+
|
39
51
|
"""
|
40
52
|
|
41
53
|
question_name: str = QuestionNameDescriptor()
|
@@ -45,36 +57,16 @@ class QuestionBase(
|
|
45
57
|
_question_presentation = None
|
46
58
|
|
47
59
|
@property
|
48
|
-
def
|
49
|
-
if self._response_model is not None:
|
50
|
-
return self._response_model
|
51
|
-
else:
|
52
|
-
return self.create_response_model()
|
53
|
-
|
54
|
-
# region: Validation and simulation methods
|
55
|
-
@property
|
56
|
-
def response_validator(self) -> "ResponseValidatorBase":
|
60
|
+
def response_validator(self) -> "ResponseValidatorABC":
|
57
61
|
"""Return the response validator."""
|
58
|
-
|
59
|
-
{
|
60
|
-
"response_model": self.response_model,
|
61
|
-
}
|
62
|
-
| {k: getattr(self, k) for k in self.validator_parameters}
|
63
|
-
| {"exception_to_throw": getattr(self, "exception_to_throw", None)}
|
64
|
-
| {"override_answer": getattr(self, "override_answer", None)}
|
65
|
-
)
|
66
|
-
return self.response_validator_class(**params)
|
62
|
+
from edsl.questions.response_validator_factory import ResponseValidatorFactory
|
67
63
|
|
68
|
-
|
69
|
-
|
70
|
-
"""Return the parameters required for the response validator.
|
64
|
+
rvf = ResponseValidatorFactory(self)
|
65
|
+
return rvf.response_validator
|
71
66
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
"""
|
77
|
-
return self.response_validator_class.required_params
|
67
|
+
def duplicate(self):
|
68
|
+
"""Return a duplicate of the question."""
|
69
|
+
return self.from_dict(self.to_dict())
|
78
70
|
|
79
71
|
@property
|
80
72
|
def fake_data_factory(self):
|
@@ -82,8 +74,7 @@ class QuestionBase(
|
|
82
74
|
if not hasattr(self, "_fake_data_factory"):
|
83
75
|
from polyfactory.factories.pydantic_factory import ModelFactory
|
84
76
|
|
85
|
-
class FakeData(ModelFactory[self.response_model]):
|
86
|
-
...
|
77
|
+
class FakeData(ModelFactory[self.response_model]): ...
|
87
78
|
|
88
79
|
self._fake_data_factory = FakeData
|
89
80
|
return self._fake_data_factory
|
@@ -110,17 +101,14 @@ class QuestionBase(
|
|
110
101
|
self, answer: dict, replacement_dict: dict = None
|
111
102
|
) -> ValidatedAnswer:
|
112
103
|
"""Validate the answer.
|
113
|
-
>>> from edsl.exceptions import QuestionAnswerValidationError
|
114
|
-
>>> from edsl import QuestionFreeText as Q
|
104
|
+
>>> from edsl.exceptions.questions import QuestionAnswerValidationError
|
105
|
+
>>> from edsl.questions import QuestionFreeText as Q
|
115
106
|
>>> Q.example()._validate_answer({'answer': 'Hello', 'generated_tokens': 'Hello'})
|
116
107
|
{'answer': 'Hello', 'generated_tokens': 'Hello'}
|
117
108
|
"""
|
118
109
|
|
119
110
|
return self.response_validator.validate(answer, replacement_dict)
|
120
111
|
|
121
|
-
# endregion
|
122
|
-
|
123
|
-
# region: Serialization methods
|
124
112
|
@property
|
125
113
|
def name(self) -> str:
|
126
114
|
"Helper function so questions and instructions can use the same access method"
|
@@ -141,7 +129,7 @@ class QuestionBase(
|
|
141
129
|
def data(self) -> dict:
|
142
130
|
"""Return a dictionary of question attributes **except** for question_type.
|
143
131
|
|
144
|
-
>>> from edsl import QuestionFreeText as Q
|
132
|
+
>>> from edsl.questions import QuestionFreeText as Q
|
145
133
|
>>> Q.example().data
|
146
134
|
{'question_name': 'how_are_you', 'question_text': 'How are you?'}
|
147
135
|
"""
|
@@ -183,10 +171,10 @@ class QuestionBase(
|
|
183
171
|
|
184
172
|
return candidate_data
|
185
173
|
|
186
|
-
def to_dict(self, add_edsl_version=True):
|
174
|
+
def to_dict(self, add_edsl_version: bool = True):
|
187
175
|
"""Convert the question to a dictionary that includes the question type (used in deserialization).
|
188
176
|
|
189
|
-
>>> from edsl import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
|
177
|
+
>>> from edsl.questions import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
|
190
178
|
{'question_name': 'how_are_you', 'question_text': 'How are you?', 'question_type': 'free_text'}
|
191
179
|
"""
|
192
180
|
candidate_data = self.data.copy()
|
@@ -237,9 +225,6 @@ class QuestionBase(
|
|
237
225
|
|
238
226
|
return question_class(**local_data)
|
239
227
|
|
240
|
-
# endregion
|
241
|
-
|
242
|
-
# region: Running methods
|
243
228
|
@classmethod
|
244
229
|
def _get_test_model(self, canned_response: Optional[str] = None) -> "LanguageModel":
|
245
230
|
"""Get a test model for the question."""
|
@@ -264,12 +249,10 @@ class QuestionBase(
|
|
264
249
|
>>> m.execute_model_call("", "")
|
265
250
|
{'message': [{'text': "Yo, what's up?"}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
|
266
251
|
>>> Q.run_example(show_answer = True, model = m, disable_remote_cache = True, disable_remote_inference = True)
|
267
|
-
answer.how_are_you
|
268
|
-
--------------------
|
269
|
-
Yo, what's up?
|
252
|
+
Dataset([{'answer.how_are_you': ["Yo, what's up?"]}])
|
270
253
|
"""
|
271
254
|
if model is None:
|
272
|
-
from edsl import Model
|
255
|
+
from edsl.language_models.model import Model
|
273
256
|
|
274
257
|
model = Model()
|
275
258
|
results = (
|
@@ -282,26 +265,28 @@ class QuestionBase(
|
|
282
265
|
)
|
283
266
|
)
|
284
267
|
if show_answer:
|
285
|
-
return results.select("answer.*")
|
268
|
+
return results.select("answer.*")
|
286
269
|
else:
|
287
270
|
return results
|
288
271
|
|
289
272
|
def __call__(
|
290
273
|
self,
|
291
|
-
just_answer=True,
|
292
|
-
model=None,
|
293
|
-
agent=None,
|
274
|
+
just_answer: bool = True,
|
275
|
+
model: Optional["LanguageModel"] = None,
|
276
|
+
agent: Optional["Agent"] = None,
|
294
277
|
disable_remote_cache: bool = False,
|
295
278
|
disable_remote_inference: bool = False,
|
279
|
+
verbose: bool = False,
|
296
280
|
**kwargs,
|
297
|
-
):
|
281
|
+
) -> Union[Any, "Results"]:
|
298
282
|
"""Call the question.
|
299
283
|
|
300
284
|
|
301
285
|
>>> from edsl import QuestionFreeText as Q
|
302
|
-
>>>
|
286
|
+
>>> from edsl import Model
|
287
|
+
>>> m = Model("test", canned_response = "Yo, what's up?")
|
303
288
|
>>> q = Q(question_name = "color", question_text = "What is your favorite color?")
|
304
|
-
>>> q(model = m, disable_remote_cache = True, disable_remote_inference = True)
|
289
|
+
>>> q(model = m, disable_remote_cache = True, disable_remote_inference = True, cache = False)
|
305
290
|
"Yo, what's up?"
|
306
291
|
|
307
292
|
"""
|
@@ -310,7 +295,7 @@ class QuestionBase(
|
|
310
295
|
model=model,
|
311
296
|
agent=agent,
|
312
297
|
**kwargs,
|
313
|
-
|
298
|
+
verbose=verbose,
|
314
299
|
disable_remote_cache=disable_remote_cache,
|
315
300
|
disable_remote_inference=disable_remote_inference,
|
316
301
|
)
|
@@ -321,15 +306,16 @@ class QuestionBase(
|
|
321
306
|
|
322
307
|
def run(self, *args, **kwargs) -> "Results":
|
323
308
|
"""Turn a single question into a survey and runs it."""
|
324
|
-
|
309
|
+
return self.to_survey().run(*args, **kwargs)
|
325
310
|
|
326
|
-
|
327
|
-
|
311
|
+
def using(self, *args, **kwargs) -> "Jobs":
|
312
|
+
"""Turn a single question into a survey and then a Job."""
|
313
|
+
return self.to_survey().to_jobs().using(*args, **kwargs)
|
328
314
|
|
329
315
|
async def run_async(
|
330
316
|
self,
|
331
317
|
just_answer: bool = True,
|
332
|
-
model: Optional["
|
318
|
+
model: Optional["LanguageModel"] = None,
|
333
319
|
agent: Optional["Agent"] = None,
|
334
320
|
disable_remote_inference: bool = False,
|
335
321
|
**kwargs,
|
@@ -337,10 +323,10 @@ class QuestionBase(
|
|
337
323
|
"""Call the question asynchronously.
|
338
324
|
|
339
325
|
>>> import asyncio
|
340
|
-
>>> from edsl import QuestionFreeText as Q
|
326
|
+
>>> from edsl.questions import QuestionFreeText as Q
|
341
327
|
>>> m = Q._get_test_model(canned_response = "Blue")
|
342
328
|
>>> q = Q(question_name = "color", question_text = "What is your favorite color?")
|
343
|
-
>>> async def test_run_async(): result = await q.run_async(model=m, disable_remote_inference = True); print(result)
|
329
|
+
>>> async def test_run_async(): result = await q.run_async(model=m, disable_remote_inference = True, disable_remote_cache = True); print(result)
|
344
330
|
>>> asyncio.run(test_run_async())
|
345
331
|
Blue
|
346
332
|
"""
|
@@ -356,27 +342,6 @@ class QuestionBase(
|
|
356
342
|
else:
|
357
343
|
return results
|
358
344
|
|
359
|
-
# endregion
|
360
|
-
|
361
|
-
# region: Magic methods
|
362
|
-
def _repr_html_(self):
|
363
|
-
# from edsl.utilities.utilities import data_to_html
|
364
|
-
|
365
|
-
data = self.to_dict(add_edsl_version=False)
|
366
|
-
# keys = list(data.keys())
|
367
|
-
# values = list(data.values())
|
368
|
-
from tabulate import tabulate
|
369
|
-
|
370
|
-
return tabulate(data.items(), headers=["keys", "values"], tablefmt="html")
|
371
|
-
|
372
|
-
# try:
|
373
|
-
# _ = data.pop("edsl_version")
|
374
|
-
# _ = data.pop("edsl_class_name")
|
375
|
-
# except KeyError:
|
376
|
-
# print("Serialized question lacks edsl version, but is should have it.")
|
377
|
-
|
378
|
-
# return data_to_html(data)
|
379
|
-
|
380
345
|
def __getitem__(self, key: str) -> Any:
|
381
346
|
"""Get an attribute of the question so it can be treated like a dictionary.
|
382
347
|
|
@@ -384,7 +349,10 @@ class QuestionBase(
|
|
384
349
|
>>> Q.example()['question_text']
|
385
350
|
'How are you?'
|
386
351
|
"""
|
387
|
-
|
352
|
+
try:
|
353
|
+
return getattr(self, key)
|
354
|
+
except TypeError:
|
355
|
+
raise KeyError(f"Question has no attribute {key} of type {type(key)}")
|
388
356
|
|
389
357
|
def __repr__(self) -> str:
|
390
358
|
"""Return a string representation of the question. Should be able to be used to reconstruct the question.
|
@@ -414,9 +382,7 @@ class QuestionBase(
|
|
414
382
|
False
|
415
383
|
|
416
384
|
"""
|
417
|
-
|
418
|
-
return False
|
419
|
-
return self.to_dict() == other.to_dict()
|
385
|
+
return hash(self) == hash(other)
|
420
386
|
|
421
387
|
def __sub__(self, other) -> BaseDiff:
|
422
388
|
"""Return the difference between two objects.
|
@@ -433,35 +399,18 @@ class QuestionBase(
|
|
433
399
|
def __add__(self, other_question_or_diff):
|
434
400
|
"""
|
435
401
|
Compose two questions into a single question.
|
436
|
-
|
437
|
-
TODO: Probably getting deprecated.
|
438
|
-
|
439
402
|
"""
|
440
403
|
if isinstance(other_question_or_diff, BaseDiff) or isinstance(
|
441
404
|
other_question_or_diff, BaseDiffCollection
|
442
405
|
):
|
443
406
|
return other_question_or_diff.apply(self)
|
444
407
|
|
445
|
-
# from edsl.questions import compose_questions
|
446
|
-
# return compose_questions(self, other_question_or_diff)
|
447
|
-
|
448
|
-
# def _validate_response(self, response):
|
449
|
-
# """Validate the response from the LLM. Behavior depends on the question type."""
|
450
|
-
# if "answer" not in response:
|
451
|
-
# raise QuestionResponseValidationError(
|
452
|
-
# "Response from LLM does not have an answer"
|
453
|
-
# )
|
454
|
-
# return response
|
455
|
-
|
456
408
|
def _translate_answer_code_to_answer(
|
457
409
|
self, answer, scenario: Optional["Scenario"] = None
|
458
410
|
):
|
459
411
|
"""There is over-ridden by child classes that ask for codes."""
|
460
412
|
return answer
|
461
413
|
|
462
|
-
# endregion
|
463
|
-
|
464
|
-
# region: Forward methods
|
465
414
|
def add_question(self, other: QuestionBase) -> "Survey":
|
466
415
|
"""Add a question to this question by turning them into a survey with two questions.
|
467
416
|
|
@@ -471,10 +420,7 @@ class QuestionBase(
|
|
471
420
|
>>> len(s.questions)
|
472
421
|
2
|
473
422
|
"""
|
474
|
-
|
475
|
-
|
476
|
-
s = Survey([self, other])
|
477
|
-
return s
|
423
|
+
return self.to_survey().add_question(other)
|
478
424
|
|
479
425
|
def to_survey(self) -> "Survey":
|
480
426
|
"""Turn a single question into a survey.
|
@@ -484,8 +430,7 @@ class QuestionBase(
|
|
484
430
|
"""
|
485
431
|
from edsl.surveys.Survey import Survey
|
486
432
|
|
487
|
-
|
488
|
-
return s
|
433
|
+
return Survey([self])
|
489
434
|
|
490
435
|
def by(self, *args) -> "Jobs":
|
491
436
|
"""Turn a single question into a survey and then a Job."""
|
@@ -494,15 +439,6 @@ class QuestionBase(
|
|
494
439
|
s = Survey([self])
|
495
440
|
return s.by(*args)
|
496
441
|
|
497
|
-
# endregion
|
498
|
-
|
499
|
-
# region: Display methods
|
500
|
-
def print(self):
|
501
|
-
from rich import print_json
|
502
|
-
import json
|
503
|
-
|
504
|
-
print_json(json.dumps(self.to_dict()))
|
505
|
-
|
506
442
|
def human_readable(self) -> str:
|
507
443
|
"""Print the question in a human readable format.
|
508
444
|
|
@@ -529,97 +465,15 @@ class QuestionBase(
|
|
529
465
|
width: Optional[int] = None,
|
530
466
|
iframe=False,
|
531
467
|
):
|
532
|
-
|
533
|
-
from jinja2 import Template
|
534
|
-
|
535
|
-
if scenario is None:
|
536
|
-
scenario = {}
|
537
|
-
|
538
|
-
prior_answers_dict = {}
|
539
|
-
|
540
|
-
if isinstance(answers, dict):
|
541
|
-
for key, value in answers.items():
|
542
|
-
if not key.endswith("_comment") and not key.endswith(
|
543
|
-
"_generated_tokens"
|
544
|
-
):
|
545
|
-
prior_answers_dict[key] = {"answer": value}
|
546
|
-
|
547
|
-
# breakpoint()
|
548
|
-
|
549
|
-
base_template = """
|
550
|
-
<div id="{{ question_name }}" class="survey_question" data-type="{{ question_type }}">
|
551
|
-
{% if include_question_name %}
|
552
|
-
<p>question_name: {{ question_name }}</p>
|
553
|
-
{% endif %}
|
554
|
-
<p class="question_text">{{ question_text }}</p>
|
555
|
-
{{ question_content }}
|
556
|
-
</div>
|
557
|
-
"""
|
558
|
-
if not hasattr(self, "question_type"):
|
559
|
-
self.question_type = "unknown"
|
560
|
-
|
561
|
-
if hasattr(self, "question_html_content"):
|
562
|
-
question_content = self.question_html_content
|
563
|
-
else:
|
564
|
-
question_content = Template("")
|
565
|
-
|
566
|
-
base_template = Template(base_template)
|
567
|
-
|
568
|
-
context = {
|
569
|
-
"scenario": scenario,
|
570
|
-
"agent": agent,
|
571
|
-
} | prior_answers_dict
|
468
|
+
from edsl.questions.HTMLQuestion import HTMLQuestion
|
572
469
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
except Exception as e:
|
577
|
-
print(
|
578
|
-
f"Error rendering question: question_text = {self.question_text}, error = {e}"
|
579
|
-
)
|
580
|
-
question_text = self.question_text
|
581
|
-
|
582
|
-
try:
|
583
|
-
question_content = Template(question_content).render(context)
|
584
|
-
except Exception as e:
|
585
|
-
print(
|
586
|
-
f"Error rendering question: question_content = {question_content}, error = {e}"
|
587
|
-
)
|
588
|
-
question_content = question_content
|
589
|
-
|
590
|
-
try:
|
591
|
-
params = {
|
592
|
-
"question_name": self.question_name,
|
593
|
-
"question_text": question_text,
|
594
|
-
"question_type": self.question_type,
|
595
|
-
"question_content": question_content,
|
596
|
-
"include_question_name": include_question_name,
|
597
|
-
}
|
598
|
-
except Exception as e:
|
599
|
-
raise ValueError(
|
600
|
-
f"Error rendering question: params = {params}, error = {e}"
|
601
|
-
)
|
602
|
-
rendered_html = base_template.render(**params)
|
603
|
-
|
604
|
-
if iframe:
|
605
|
-
import html
|
606
|
-
from IPython.display import display, HTML
|
607
|
-
|
608
|
-
height = height or 200
|
609
|
-
width = width or 600
|
610
|
-
escaped_output = html.escape(rendered_html)
|
611
|
-
# escaped_output = rendered_html
|
612
|
-
iframe = f""""
|
613
|
-
<iframe srcdoc="{ escaped_output }" style="width: {width}px; height: {height}px;"></iframe>
|
614
|
-
"""
|
615
|
-
display(HTML(iframe))
|
616
|
-
return None
|
617
|
-
|
618
|
-
return rendered_html
|
470
|
+
return HTMLQuestion(self).html(
|
471
|
+
scenario, agent, answers, include_question_name, height, width, iframe
|
472
|
+
)
|
619
473
|
|
620
474
|
@classmethod
|
621
475
|
def example_model(cls):
|
622
|
-
from edsl import Model
|
476
|
+
from edsl.language_models.model import Model
|
623
477
|
|
624
478
|
q = cls.example()
|
625
479
|
m = Model("test", canned_response=cls._simulate_answer(q)["answer"])
|
@@ -1,8 +1,6 @@
|
|
1
1
|
from importlib import resources
|
2
2
|
from typing import Optional
|
3
|
-
from edsl.prompts import Prompt
|
4
3
|
from edsl.exceptions.questions import QuestionAnswerValidationError
|
5
|
-
|
6
4
|
from functools import lru_cache
|
7
5
|
|
8
6
|
|
@@ -71,7 +69,7 @@ class QuestionBasePromptsMixin:
|
|
71
69
|
>>> q.get_instructions(model = "gpt3")
|
72
70
|
Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
|
73
71
|
"""
|
74
|
-
from edsl import Model
|
72
|
+
from edsl.language_models.model import Model
|
75
73
|
|
76
74
|
if not hasattr(self, "_model_instructions"):
|
77
75
|
self._model_instructions = {}
|
@@ -122,6 +120,8 @@ class QuestionBasePromptsMixin:
|
|
122
120
|
template_text = template_manager.get_template(
|
123
121
|
cls.question_type, "answering_instructions.jinja"
|
124
122
|
)
|
123
|
+
from edsl.prompts import Prompt
|
124
|
+
|
125
125
|
return Prompt(text=template_text)
|
126
126
|
|
127
127
|
@classmethod
|
@@ -129,6 +129,8 @@ class QuestionBasePromptsMixin:
|
|
129
129
|
template_text = template_manager.get_template(
|
130
130
|
cls.question_type, "question_presentation.jinja"
|
131
131
|
)
|
132
|
+
from edsl.prompts import Prompt
|
133
|
+
|
132
134
|
return Prompt(text=template_text)
|
133
135
|
|
134
136
|
@property
|
@@ -182,6 +184,8 @@ class QuestionBasePromptsMixin:
|
|
182
184
|
@property
|
183
185
|
def new_default_instructions(self) -> "Prompt":
|
184
186
|
"This is set up as a property because there are mutable question values that determine how it is rendered."
|
187
|
+
from edsl.prompts import Prompt
|
188
|
+
|
185
189
|
return Prompt(self.question_presentation) + Prompt(self.answering_instructions)
|
186
190
|
|
187
191
|
@property
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -5,7 +5,7 @@ from pydantic import Field, BaseModel, validator
|
|
5
5
|
|
6
6
|
from edsl.questions.QuestionBase import QuestionBase
|
7
7
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
8
|
-
from edsl.questions.
|
8
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
9
9
|
|
10
10
|
|
11
11
|
class BudgewResponseValidator(ResponseValidatorABC):
|
@@ -13,10 +13,10 @@ from edsl.questions.descriptors import (
|
|
13
13
|
from edsl.questions.decorators import inject_exception
|
14
14
|
|
15
15
|
from pydantic import field_validator
|
16
|
-
from edsl.questions.
|
17
|
-
from edsl.questions.
|
16
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
17
|
+
from edsl.questions.data_structures import BaseResponse
|
18
18
|
|
19
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
19
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
20
20
|
|
21
21
|
from pydantic import BaseModel, Field, conlist
|
22
22
|
from typing import List, Literal, Optional, Annotated
|
@@ -6,9 +6,8 @@ from typing import Any, Optional, Dict
|
|
6
6
|
from edsl.questions.QuestionBase import QuestionBase
|
7
7
|
from edsl.questions.descriptors import AnswerTemplateDescriptor
|
8
8
|
|
9
|
-
from edsl.questions.
|
10
|
-
from edsl.questions.
|
11
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
9
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
10
|
+
from edsl.questions.data_structures import BaseResponse
|
12
11
|
from edsl.questions.decorators import inject_exception
|
13
12
|
|
14
13
|
from typing import Dict, Any
|
@@ -57,7 +56,7 @@ def dict_to_pydantic_model(input_dict: Dict[str, Any]) -> Any:
|
|
57
56
|
DynamicModel = create_model("DynamicModel", **field_definitions)
|
58
57
|
|
59
58
|
class AnswerModel(BaseResponse):
|
60
|
-
answer: DynamicModel
|
59
|
+
answer: "DynamicModel"
|
61
60
|
generated_tokens: Optional[str] = None
|
62
61
|
comment: Optional[str] = None
|
63
62
|
|
@@ -113,6 +112,8 @@ class QuestionExtract(QuestionBase):
|
|
113
112
|
:param question_name: The name of the question.
|
114
113
|
:param question_text: The text of the question.
|
115
114
|
:param answer_template: The template for the answer.
|
115
|
+
:param answering_instructions: Instructions for answering the question.
|
116
|
+
:param question_presentation: The presentation of the question.
|
116
117
|
"""
|
117
118
|
self.question_name = question_name
|
118
119
|
self.question_text = question_text
|
@@ -142,9 +143,6 @@ class QuestionExtract(QuestionBase):
|
|
142
143
|
)
|
143
144
|
return question_html_content
|
144
145
|
|
145
|
-
################
|
146
|
-
# Helpful methods
|
147
|
-
################
|
148
146
|
@classmethod
|
149
147
|
@inject_exception
|
150
148
|
def example(cls) -> QuestionExtract:
|
@@ -5,15 +5,14 @@ from uuid import uuid4
|
|
5
5
|
from pydantic import field_validator
|
6
6
|
|
7
7
|
from edsl.questions.QuestionBase import QuestionBase
|
8
|
-
from edsl.questions.
|
8
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
9
9
|
|
10
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
10
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
11
11
|
from edsl.questions.decorators import inject_exception
|
12
12
|
|
13
13
|
from pydantic import BaseModel
|
14
14
|
from typing import Optional, Any, List
|
15
15
|
|
16
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
17
16
|
from edsl.prompts.Prompt import Prompt
|
18
17
|
|
19
18
|
|
edsl/questions/QuestionList.py
CHANGED
@@ -1,23 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import
|
3
|
-
import textwrap
|
2
|
+
import json
|
4
3
|
from typing import Any, Optional, Union
|
4
|
+
|
5
|
+
from pydantic import Field
|
6
|
+
from json_repair import repair_json
|
7
|
+
|
8
|
+
from edsl.exceptions.questions import QuestionAnswerValidationError
|
5
9
|
from edsl.questions.QuestionBase import QuestionBase
|
6
10
|
from edsl.questions.descriptors import IntegerOrNoneDescriptor
|
7
11
|
from edsl.questions.decorators import inject_exception
|
12
|
+
from edsl.questions.response_validator_abc import ResponseValidatorABC
|
8
13
|
|
9
|
-
from pydantic import field_validator, Field
|
10
|
-
from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
|
11
|
-
from edsl.questions.ResponseValidatorABC import BaseResponse
|
12
|
-
|
13
|
-
from edsl.exceptions import QuestionAnswerValidationError
|
14
|
-
import textwrap
|
15
|
-
import json
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
def convert_string(s):
|
15
|
+
def convert_string(s: str) -> Union[float, int, str, dict]:
|
21
16
|
"""Convert a string to a more appropriate type if possible.
|
22
17
|
|
23
18
|
>>> convert_string("3.14")
|
@@ -60,7 +55,7 @@ def convert_string(s):
|
|
60
55
|
return s
|
61
56
|
|
62
57
|
|
63
|
-
def create_model(max_list_items: int, permissive):
|
58
|
+
def create_model(max_list_items: int, permissive: bool) -> "ListResponse":
|
64
59
|
from pydantic import BaseModel
|
65
60
|
|
66
61
|
if permissive or max_list_items is None:
|
@@ -135,8 +130,8 @@ class QuestionList(QuestionBase):
|
|
135
130
|
self,
|
136
131
|
question_name: str,
|
137
132
|
question_text: str,
|
138
|
-
max_list_items: Optional[int] = None,
|
139
133
|
include_comment: bool = True,
|
134
|
+
max_list_items: Optional[int] = None,
|
140
135
|
answering_instructions: Optional[str] = None,
|
141
136
|
question_presentation: Optional[str] = None,
|
142
137
|
permissive: bool = False,
|
@@ -186,9 +181,6 @@ class QuestionList(QuestionBase):
|
|
186
181
|
).render(question_name=self.question_name)
|
187
182
|
return question_html_content
|
188
183
|
|
189
|
-
################
|
190
|
-
# Helpful methods
|
191
|
-
################
|
192
184
|
@classmethod
|
193
185
|
@inject_exception
|
194
186
|
def example(
|