edsl 0.1.33.dev1__py3-none-any.whl → 0.1.33.dev2__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.
Files changed (163) hide show
  1. edsl/TemplateLoader.py +24 -0
  2. edsl/__init__.py +8 -4
  3. edsl/agents/Agent.py +46 -14
  4. edsl/agents/AgentList.py +43 -0
  5. edsl/agents/Invigilator.py +125 -212
  6. edsl/agents/InvigilatorBase.py +140 -32
  7. edsl/agents/PromptConstructionMixin.py +43 -66
  8. edsl/agents/__init__.py +1 -0
  9. edsl/auto/AutoStudy.py +117 -0
  10. edsl/auto/StageBase.py +230 -0
  11. edsl/auto/StageGenerateSurvey.py +178 -0
  12. edsl/auto/StageLabelQuestions.py +125 -0
  13. edsl/auto/StagePersona.py +61 -0
  14. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  15. edsl/auto/StagePersonaDimensionValues.py +74 -0
  16. edsl/auto/StagePersonaDimensions.py +69 -0
  17. edsl/auto/StageQuestions.py +73 -0
  18. edsl/auto/SurveyCreatorPipeline.py +21 -0
  19. edsl/auto/utilities.py +224 -0
  20. edsl/config.py +38 -39
  21. edsl/coop/PriceFetcher.py +58 -0
  22. edsl/coop/coop.py +39 -5
  23. edsl/data/Cache.py +35 -1
  24. edsl/data_transfer_models.py +120 -38
  25. edsl/enums.py +2 -0
  26. edsl/exceptions/language_models.py +25 -1
  27. edsl/exceptions/questions.py +62 -5
  28. edsl/exceptions/results.py +4 -0
  29. edsl/inference_services/AnthropicService.py +13 -11
  30. edsl/inference_services/AwsBedrock.py +19 -17
  31. edsl/inference_services/AzureAI.py +37 -20
  32. edsl/inference_services/GoogleService.py +16 -12
  33. edsl/inference_services/GroqService.py +2 -0
  34. edsl/inference_services/InferenceServiceABC.py +24 -0
  35. edsl/inference_services/MistralAIService.py +120 -0
  36. edsl/inference_services/OpenAIService.py +41 -50
  37. edsl/inference_services/TestService.py +71 -0
  38. edsl/inference_services/models_available_cache.py +0 -6
  39. edsl/inference_services/registry.py +4 -0
  40. edsl/jobs/Answers.py +10 -12
  41. edsl/jobs/FailedQuestion.py +78 -0
  42. edsl/jobs/Jobs.py +18 -13
  43. edsl/jobs/buckets/TokenBucket.py +39 -14
  44. edsl/jobs/interviews/Interview.py +297 -77
  45. edsl/jobs/interviews/InterviewExceptionEntry.py +83 -19
  46. edsl/jobs/interviews/interview_exception_tracking.py +0 -70
  47. edsl/jobs/interviews/retry_management.py +3 -1
  48. edsl/jobs/runners/JobsRunnerAsyncio.py +116 -70
  49. edsl/jobs/runners/JobsRunnerStatusMixin.py +1 -1
  50. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  51. edsl/jobs/tasks/TaskHistory.py +131 -213
  52. edsl/language_models/LanguageModel.py +239 -129
  53. edsl/language_models/ModelList.py +2 -2
  54. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  55. edsl/language_models/fake_openai_call.py +15 -0
  56. edsl/language_models/fake_openai_service.py +61 -0
  57. edsl/language_models/registry.py +15 -2
  58. edsl/language_models/repair.py +0 -19
  59. edsl/language_models/utilities.py +61 -0
  60. edsl/prompts/Prompt.py +52 -2
  61. edsl/questions/AnswerValidatorMixin.py +23 -26
  62. edsl/questions/QuestionBase.py +273 -242
  63. edsl/questions/QuestionBaseGenMixin.py +133 -0
  64. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  65. edsl/questions/QuestionBudget.py +6 -0
  66. edsl/questions/QuestionCheckBox.py +227 -35
  67. edsl/questions/QuestionExtract.py +98 -27
  68. edsl/questions/QuestionFreeText.py +46 -29
  69. edsl/questions/QuestionFunctional.py +7 -0
  70. edsl/questions/QuestionList.py +141 -22
  71. edsl/questions/QuestionMultipleChoice.py +173 -64
  72. edsl/questions/QuestionNumerical.py +87 -46
  73. edsl/questions/QuestionRank.py +182 -24
  74. edsl/questions/RegisterQuestionsMeta.py +31 -12
  75. edsl/questions/ResponseValidatorABC.py +169 -0
  76. edsl/questions/__init__.py +3 -4
  77. edsl/questions/decorators.py +21 -0
  78. edsl/questions/derived/QuestionLikertFive.py +10 -5
  79. edsl/questions/derived/QuestionLinearScale.py +11 -1
  80. edsl/questions/derived/QuestionTopK.py +6 -0
  81. edsl/questions/derived/QuestionYesNo.py +16 -1
  82. edsl/questions/descriptors.py +43 -7
  83. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  84. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  85. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  86. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  87. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  88. edsl/questions/prompt_templates/question_list.jinja +17 -0
  89. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  90. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  91. edsl/questions/question_registry.py +6 -2
  92. edsl/questions/templates/__init__.py +0 -0
  93. edsl/questions/templates/checkbox/__init__.py +0 -0
  94. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  95. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  96. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  97. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  98. edsl/questions/templates/free_text/__init__.py +0 -0
  99. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  100. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  101. edsl/questions/templates/likert_five/__init__.py +0 -0
  102. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  103. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  104. edsl/questions/templates/linear_scale/__init__.py +0 -0
  105. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  106. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  107. edsl/questions/templates/list/__init__.py +0 -0
  108. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  109. edsl/questions/templates/list/question_presentation.jinja +5 -0
  110. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  111. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  112. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  113. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  114. edsl/questions/templates/numerical/__init__.py +0 -0
  115. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  116. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  117. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  118. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  119. edsl/questions/templates/top_k/__init__.py +0 -0
  120. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  121. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  122. edsl/questions/templates/yes_no/__init__.py +0 -0
  123. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  124. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  125. edsl/results/Dataset.py +20 -0
  126. edsl/results/DatasetExportMixin.py +41 -47
  127. edsl/results/DatasetTree.py +145 -0
  128. edsl/results/Result.py +32 -5
  129. edsl/results/Results.py +131 -45
  130. edsl/results/ResultsDBMixin.py +3 -3
  131. edsl/results/Selector.py +118 -0
  132. edsl/results/tree_explore.py +115 -0
  133. edsl/scenarios/Scenario.py +10 -4
  134. edsl/scenarios/ScenarioList.py +348 -39
  135. edsl/scenarios/ScenarioListExportMixin.py +9 -0
  136. edsl/study/SnapShot.py +8 -1
  137. edsl/surveys/RuleCollection.py +2 -2
  138. edsl/surveys/Survey.py +634 -315
  139. edsl/surveys/SurveyExportMixin.py +71 -9
  140. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  141. edsl/surveys/SurveyQualtricsImport.py +75 -4
  142. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  143. edsl/surveys/instructions/Instruction.py +34 -0
  144. edsl/surveys/instructions/InstructionCollection.py +77 -0
  145. edsl/surveys/instructions/__init__.py +0 -0
  146. edsl/templates/error_reporting/base.html +24 -0
  147. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  148. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  149. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  150. edsl/templates/error_reporting/interview_details.html +111 -0
  151. edsl/templates/error_reporting/interviews.html +10 -0
  152. edsl/templates/error_reporting/overview.html +5 -0
  153. edsl/templates/error_reporting/performance_plot.html +2 -0
  154. edsl/templates/error_reporting/report.css +74 -0
  155. edsl/templates/error_reporting/report.html +118 -0
  156. edsl/templates/error_reporting/report.js +25 -0
  157. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +4 -2
  158. edsl-0.1.33.dev2.dist-info/RECORD +289 -0
  159. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
  160. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  161. edsl-0.1.33.dev1.dist-info/RECORD +0 -209
  162. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
  163. {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+ import copy
3
+ import itertools
4
+ from typing import Optional, List, Callable, Type
5
+ from typing import TypeVar
6
+
7
+
8
+ class QuestionBaseGenMixin:
9
+ def copy(self) -> QuestionBase:
10
+ """Return a deep copy of the question.
11
+
12
+ >>> from edsl.questions import QuestionFreeText
13
+ >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
14
+ >>> q2 = q.copy()
15
+ >>> q2.question_name
16
+ 'color'
17
+
18
+ """
19
+ return copy.deepcopy(self)
20
+
21
+ def option_permutations(self) -> list[QuestionBase]:
22
+ """Return a list of questions with all possible permutations of the options.
23
+
24
+ >>> from edsl import QuestionMultipleChoice as Q
25
+ >>> len(Q.example().option_permutations())
26
+ 24
27
+ """
28
+
29
+ if not hasattr(self, "question_options"):
30
+ return [self]
31
+
32
+ questions = []
33
+ for index, permutation in enumerate(
34
+ itertools.permutations(self.question_options)
35
+ ):
36
+ question = copy.deepcopy(self)
37
+ question.question_options = list(permutation)
38
+ question.question_name = f"{self.question_name}_{index}"
39
+ questions.append(question)
40
+ return questions
41
+
42
+ def loop(self, scenario_list: ScenarioList) -> List[QuestionBase]:
43
+ """Return a list of questions with the question name modified for each scenario.
44
+
45
+ :param scenario_list: The list of scenarios to loop through.
46
+
47
+ >>> from edsl import QuestionFreeText
48
+ >>> from edsl import ScenarioList
49
+ >>> q = QuestionFreeText(question_text = "What are your thoughts on: {{ subject}}?", question_name = "base_{{subject}}")
50
+ >>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
51
+ 3
52
+
53
+ """
54
+ from jinja2 import Environment
55
+ from edsl.questions.QuestionBase import QuestionBase
56
+
57
+ starting_name = self.question_name
58
+ questions = []
59
+ for index, scenario in enumerate(scenario_list):
60
+ env = Environment()
61
+ new_data = self.to_dict().copy()
62
+ for key, value in [(k, v) for k, v in new_data.items() if v is not None]:
63
+ if (
64
+ isinstance(value, str) or isinstance(value, int)
65
+ ) and key != "question_options":
66
+ new_data[key] = env.from_string(value).render(scenario)
67
+ elif isinstance(value, list):
68
+ new_data[key] = [
69
+ env.from_string(v).render(scenario) if isinstance(v, str) else v
70
+ for v in value
71
+ ]
72
+ elif isinstance(value, dict):
73
+ new_data[key] = {
74
+ (
75
+ env.from_string(k).render(scenario)
76
+ if isinstance(k, str)
77
+ else k
78
+ ): (
79
+ env.from_string(v).render(scenario)
80
+ if isinstance(v, str)
81
+ else v
82
+ )
83
+ for k, v in value.items()
84
+ }
85
+ elif key == "question_options" and isinstance(value, str):
86
+ new_data[key] = value
87
+ else:
88
+ raise ValueError(
89
+ f"Unexpected value type: {type(value)} for key '{key}'"
90
+ )
91
+
92
+ if new_data["question_name"] == starting_name:
93
+ new_data["question_name"] = new_data["question_name"] + f"_{index}"
94
+
95
+ questions.append(QuestionBase.from_dict(new_data))
96
+ return questions
97
+
98
+ def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
99
+ """Apply a function to the question parts
100
+
101
+ >>> from edsl.questions import QuestionFreeText
102
+ >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
103
+ >>> shouting = lambda x: x.upper()
104
+ >>> q.apply_function(shouting)
105
+ Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
106
+
107
+ """
108
+ from edsl.questions.QuestionBase import QuestionBase
109
+
110
+ if exclude_components is None:
111
+ exclude_components = ["question_name", "question_type"]
112
+
113
+ d = copy.deepcopy(self._to_dict())
114
+ for key, value in d.items():
115
+ if key in exclude_components:
116
+ continue
117
+ if isinstance(value, dict):
118
+ for k, v in value.items():
119
+ value[k] = func(v)
120
+ d[key] = value
121
+ continue
122
+ if isinstance(value, list):
123
+ value = [func(v) for v in value]
124
+ d[key] = value
125
+ continue
126
+ d[key] = func(value)
127
+ return QuestionBase.from_dict(d)
128
+
129
+
130
+ if __name__ == "__main__":
131
+ import doctest
132
+
133
+ doctest.testmod()
@@ -0,0 +1,266 @@
1
+ from importlib import resources
2
+ from typing import Optional
3
+ from edsl.prompts import Prompt
4
+ from edsl.exceptions.questions import QuestionAnswerValidationError
5
+
6
+ from functools import lru_cache
7
+
8
+
9
+ class TemplateManager:
10
+ _instance = None
11
+
12
+ def __new__(cls):
13
+ if cls._instance is None:
14
+ cls._instance = super().__new__(cls)
15
+ cls._instance._template_cache = {}
16
+ return cls._instance
17
+
18
+ @lru_cache(maxsize=None)
19
+ def get_template(self, question_type, template_name):
20
+ if (question_type, template_name) not in self._template_cache:
21
+ with resources.open_text(
22
+ f"edsl.questions.templates.{question_type}", template_name
23
+ ) as file:
24
+ self._template_cache[(question_type, template_name)] = file.read()
25
+ return self._template_cache[(question_type, template_name)]
26
+
27
+
28
+ # Global instance
29
+ template_manager = TemplateManager()
30
+
31
+
32
+ class QuestionBasePromptsMixin:
33
+ # @classmethod
34
+ # @lru_cache(maxsize=1)
35
+ # def _read_template(cls, template_name):
36
+ # with resources.open_text(
37
+ # f"edsl.questions.templates.{cls.question_type}", template_name
38
+ # ) as file:
39
+ # return file.read()
40
+
41
+ @classmethod
42
+ def applicable_prompts(
43
+ cls, model: Optional[str] = None
44
+ ) -> list[type["PromptBase"]]:
45
+ """Get the prompts that are applicable to the question type.
46
+
47
+ :param model: The language model to use.
48
+
49
+ >>> from edsl.questions import QuestionFreeText
50
+ >>> QuestionFreeText.applicable_prompts()
51
+ [<class 'edsl.prompts.library.question_freetext.FreeText'>]
52
+
53
+ :param model: The language model to use. If None, assumes does not matter.
54
+
55
+ """
56
+ from edsl.prompts.registry import get_classes as prompt_lookup
57
+
58
+ applicable_prompts = prompt_lookup(
59
+ component_type="question_instructions",
60
+ question_type=cls.question_type,
61
+ model=model,
62
+ )
63
+ return applicable_prompts
64
+
65
+ @property
66
+ def model_instructions(self) -> dict:
67
+ """Get the model-specific instructions for the question."""
68
+ if not hasattr(self, "_model_instructions"):
69
+ self._model_instructions = {}
70
+ return self._model_instructions
71
+
72
+ def _all_text(self) -> str:
73
+ """Return the question text.
74
+
75
+ >>> from edsl import QuestionMultipleChoice as Q
76
+ >>> Q.example()._all_text()
77
+ "how_feelingHow are you?['Good', 'Great', 'OK', 'Bad']"
78
+ """
79
+ txt = ""
80
+ for key, value in self.data.items():
81
+ if isinstance(value, str):
82
+ txt += value
83
+ elif isinstance(value, list):
84
+ txt += "".join(str(value))
85
+ return txt
86
+
87
+ @model_instructions.setter
88
+ def model_instructions(self, data: dict):
89
+ """Set the model-specific instructions for the question."""
90
+ self._model_instructions = data
91
+
92
+ def add_model_instructions(
93
+ self, *, instructions: str, model: Optional[str] = None
94
+ ) -> None:
95
+ """Add model-specific instructions for the question that override the default instructions.
96
+
97
+ :param instructions: The instructions to add. This is typically a jinja2 template.
98
+ :param model: The language model for this instruction.
99
+
100
+ >>> from edsl.questions import QuestionFreeText
101
+ >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
102
+ >>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
103
+ >>> q.get_instructions(model = "gpt3")
104
+ Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
105
+ """
106
+ from edsl import Model
107
+
108
+ if not hasattr(self, "_model_instructions"):
109
+ self._model_instructions = {}
110
+ if model is None:
111
+ # if not model is passed, all the models are mapped to this instruction, including 'None'
112
+ self._model_instructions = {
113
+ model_name: instructions
114
+ for model_name in Model.available(name_only=True)
115
+ }
116
+ self._model_instructions.update({model: instructions})
117
+ else:
118
+ self._model_instructions.update({model: instructions})
119
+
120
+ @classmethod
121
+ def path_to_folder(cls) -> str:
122
+ return resources.files(f"edsl.questions.templates", cls.question_type)
123
+
124
+ @property
125
+ def response_model(self) -> type["BaseModel"]:
126
+ if self._response_model is not None:
127
+ return self._response_model
128
+ else:
129
+ return self.create_response_model()
130
+
131
+ @property
132
+ def use_code(self) -> bool:
133
+ if hasattr(self, "_use_code"):
134
+ return self._use_code
135
+ return True
136
+
137
+ @use_code.setter
138
+ def use_code(self, value: bool) -> None:
139
+ self._use_code = value
140
+
141
+ @property
142
+ def include_comment(self) -> bool:
143
+ if hasattr(self, "_include_comment"):
144
+ return self._include_comment
145
+ return True
146
+
147
+ @include_comment.setter
148
+ def include_comment(self, value: bool) -> None:
149
+ self._include_comment = value
150
+
151
+ @classmethod
152
+ def default_answering_instructions(cls) -> str:
153
+ # template_text = cls._read_template("answering_instructions.jinja")
154
+ template_text = template_manager.get_template(
155
+ cls.question_type, "answering_instructions.jinja"
156
+ )
157
+ return Prompt(text=template_text)
158
+
159
+ @classmethod
160
+ def default_question_presentation(cls):
161
+ # template_text = cls._read_template("question_presentation.jinja")
162
+ template_text = template_manager.get_template(
163
+ cls.question_type, "question_presentation.jinja"
164
+ )
165
+ return Prompt(text=template_text)
166
+
167
+ @property
168
+ def answering_instructions(self) -> str:
169
+ if self._answering_instructions is None:
170
+ return self.default_answering_instructions()
171
+ return self._answering_instructions
172
+
173
+ @answering_instructions.setter
174
+ def answering_instructions(self, value) -> None:
175
+ self._answering_instructions = value
176
+
177
+ # @classmethod
178
+ # def default_answering_instructions(cls) -> str:
179
+ # with resources.open_text(
180
+ # f"edsl.questions.templates.{cls.question_type}",
181
+ # "answering_instructions.jinja",
182
+ # ) as file:
183
+ # return Prompt(text=file.read())
184
+
185
+ # @classmethod
186
+ # def default_question_presentation(cls):
187
+ # with resources.open_text(
188
+ # f"edsl.questions.templates.{cls.question_type}",
189
+ # "question_presentation.jinja",
190
+ # ) as file:
191
+ # return Prompt(text=file.read())
192
+
193
+ @property
194
+ def question_presentation(self):
195
+ if self._question_presentation is None:
196
+ return self.default_question_presentation()
197
+ return self._question_presentation
198
+
199
+ @question_presentation.setter
200
+ def question_presentation(self, value):
201
+ self._question_presentation = value
202
+
203
+ def prompt_preview(self, scenario=None, agent=None):
204
+ return self.new_default_instructions.render(
205
+ self.data
206
+ | {
207
+ "include_comment": getattr(self, "_include_comment", True),
208
+ "use_code": getattr(self, "_use_code", True),
209
+ }
210
+ | ({"scenario": scenario} or {})
211
+ | ({"agent": agent} or {})
212
+ )
213
+
214
+ @classmethod
215
+ def self_check(cls):
216
+ q = cls.example()
217
+ for answer, params in q.response_validator.valid_examples:
218
+ for key, value in params.items():
219
+ setattr(q, key, value)
220
+ q._validate_answer(answer)
221
+ for answer, params, reason in q.response_validator.invalid_examples:
222
+ for key, value in params.items():
223
+ setattr(q, key, value)
224
+ try:
225
+ q._validate_answer(answer)
226
+ except QuestionAnswerValidationError:
227
+ pass
228
+ else:
229
+ raise ValueError(f"Example {answer} should have failed for {reason}.")
230
+
231
+ @property
232
+ def new_default_instructions(self) -> "Prompt":
233
+ "This is set up as a property because there are mutable question values that determine how it is rendered."
234
+ return self.question_presentation + self.answering_instructions
235
+
236
+ @property
237
+ def parameters(self) -> set[str]:
238
+ """Return the parameters of the question."""
239
+ from jinja2 import Environment, meta
240
+
241
+ env = Environment()
242
+ # Parse the template
243
+ txt = self._all_text()
244
+ # txt = self.question_text
245
+ # if hasattr(self, "question_options"):
246
+ # txt += " ".join(self.question_options)
247
+ parsed_content = env.parse(txt)
248
+ # Extract undeclared variables
249
+ variables = meta.find_undeclared_variables(parsed_content)
250
+ # Return as a list
251
+ return set(variables)
252
+
253
+ def get_instructions(self, model: Optional[str] = None) -> type["PromptBase"]:
254
+ """Get the mathcing question-answering instructions for the question.
255
+
256
+ :param model: The language model to use.
257
+ """
258
+ from edsl.prompts.Prompt import Prompt
259
+
260
+ if model in self.model_instructions:
261
+ return Prompt(text=self.model_instructions[model])
262
+ else:
263
+ if hasattr(self, "new_default_instructions"):
264
+ return self.new_default_instructions
265
+ else:
266
+ return self.applicable_prompts(model)[0]()
@@ -11,6 +11,8 @@ class QuestionBudget(QuestionBase):
11
11
  question_type = "budget"
12
12
  budget_sum: int = IntegerDescriptor(none_allowed=False)
13
13
  question_options: list[str] = QuestionOptionsDescriptor(q_budget=True)
14
+ _response_model = None
15
+ response_validator_class = None
14
16
 
15
17
  def __init__(
16
18
  self,
@@ -18,6 +20,8 @@ class QuestionBudget(QuestionBase):
18
20
  question_text: str,
19
21
  question_options: list[str],
20
22
  budget_sum: int,
23
+ question_presentation: Optional[str] = None,
24
+ answering_instructions: Optional[str] = None,
21
25
  ):
22
26
  """Instantiate a new QuestionBudget.
23
27
 
@@ -30,6 +34,8 @@ class QuestionBudget(QuestionBase):
30
34
  self.question_text = question_text
31
35
  self.question_options = question_options
32
36
  self.budget_sum = budget_sum
37
+ self.question_presentation = question_presentation
38
+ self.answering_instructions = answering_instructions
33
39
 
34
40
  ################
35
41
  # Answer methods