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.
Files changed (212) hide show
  1. edsl/Base.py +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
@@ -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.AnswerValidatorMixin import AnswerValidatorMixin
17
- from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
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.QuestionBaseGenMixin import QuestionBaseGenMixin
24
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
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
- RichPrintingMixin,
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 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
-
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
- params = (
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
- @property
69
- def validator_parameters(self) -> list[str]:
70
- """Return the parameters required for the response validator.
64
+ rvf = ResponseValidatorFactory(self)
65
+ return rvf.response_validator
71
66
 
72
- >>> from edsl import QuestionMultipleChoice as Q
73
- >>> Q.example().validator_parameters
74
- ['question_options', 'use_code']
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.*").print()
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
- >>> m = Q._get_test_model(canned_response = "Yo, what's up?")
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
- cache=False,
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
- from edsl.surveys.Survey import Survey
309
+ return self.to_survey().run(*args, **kwargs)
325
310
 
326
- s = self.to_survey()
327
- return s.run(*args, **kwargs)
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["Model"] = None,
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
- return getattr(self, key)
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
- if not isinstance(other, QuestionBase):
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
- from edsl.surveys.Survey import Survey
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
- s = Survey([self])
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
- """Return the question in HTML format."""
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
- # Render the question text
574
- try:
575
- question_text = Template(self.question_text).render(context)
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
@@ -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.ResponseValidatorABC import ResponseValidatorABC
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.ResponseValidatorABC import ResponseValidatorABC
17
- from edsl.questions.ResponseValidatorABC import BaseResponse
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.ResponseValidatorABC import ResponseValidatorABC
10
- from edsl.questions.ResponseValidatorABC import BaseResponse
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.ResponseValidatorABC import ResponseValidatorABC
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
 
@@ -1,23 +1,18 @@
1
1
  from __future__ import annotations
2
- import random
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
- from json_repair import repair_json
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(