edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.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 (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
@@ -3,30 +3,26 @@
3
3
  from __future__ import annotations
4
4
  from abc import ABC, abstractmethod
5
5
  from typing import Any, Type, Optional, List, Callable, Union, TypedDict
6
- import copy
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
12
  from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
17
13
  from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
18
- from edsl.Base import PersistenceMixin, RichPrintingMixin
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
19
  from edsl.questions.QuestionBaseGenMixin import QuestionBaseGenMixin
24
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
20
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
25
21
 
26
22
 
27
23
  class QuestionBase(
28
24
  PersistenceMixin,
29
- RichPrintingMixin,
25
+ RepresentationMixin,
30
26
  SimpleAskMixin,
31
27
  QuestionBasePromptsMixin,
32
28
  QuestionBaseGenMixin,
@@ -36,6 +32,14 @@ class QuestionBase(
36
32
  ):
37
33
  """ABC for the Question class. All questions inherit from this class.
38
34
  Some of the constraints on child questions are defined in the RegisterQuestionsMeta metaclass.
35
+
36
+
37
+ Every child class wiill have class attributes of question_type, _response_model and response_validator_class e.g.,
38
+
39
+ question_type = "free_text"
40
+ _response_model = FreeTextResponse
41
+ response_validator_class = FreeTextResponseValidator
42
+
39
43
  """
40
44
 
41
45
  question_name: str = QuestionNameDescriptor()
@@ -44,37 +48,13 @@ class QuestionBase(
44
48
  _answering_instructions = None
45
49
  _question_presentation = None
46
50
 
47
- @property
48
- def response_model(self) -> type["BaseModel"]:
49
- if self._response_model is not None:
50
- return self._response_model
51
- else:
52
- return self.create_response_model()
53
-
54
- # region: Validation and simulation methods
55
51
  @property
56
52
  def response_validator(self) -> "ResponseValidatorBase":
57
53
  """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)
67
-
68
- @property
69
- def validator_parameters(self) -> list[str]:
70
- """Return the parameters required for the response validator.
54
+ from edsl.questions.ResponseValidatorFactory import ResponseValidatorFactory
71
55
 
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
56
+ rvf = ResponseValidatorFactory(self)
57
+ return rvf.response_validator
78
58
 
79
59
  @property
80
60
  def fake_data_factory(self):
@@ -82,8 +62,7 @@ class QuestionBase(
82
62
  if not hasattr(self, "_fake_data_factory"):
83
63
  from polyfactory.factories.pydantic_factory import ModelFactory
84
64
 
85
- class FakeData(ModelFactory[self.response_model]):
86
- ...
65
+ class FakeData(ModelFactory[self.response_model]): ...
87
66
 
88
67
  self._fake_data_factory = FakeData
89
68
  return self._fake_data_factory
@@ -110,17 +89,14 @@ class QuestionBase(
110
89
  self, answer: dict, replacement_dict: dict = None
111
90
  ) -> ValidatedAnswer:
112
91
  """Validate the answer.
113
- >>> from edsl.exceptions import QuestionAnswerValidationError
114
- >>> from edsl import QuestionFreeText as Q
92
+ >>> from edsl.exceptions.questions import QuestionAnswerValidationError
93
+ >>> from edsl.questions import QuestionFreeText as Q
115
94
  >>> Q.example()._validate_answer({'answer': 'Hello', 'generated_tokens': 'Hello'})
116
95
  {'answer': 'Hello', 'generated_tokens': 'Hello'}
117
96
  """
118
97
 
119
98
  return self.response_validator.validate(answer, replacement_dict)
120
99
 
121
- # endregion
122
-
123
- # region: Serialization methods
124
100
  @property
125
101
  def name(self) -> str:
126
102
  "Helper function so questions and instructions can use the same access method"
@@ -141,7 +117,7 @@ class QuestionBase(
141
117
  def data(self) -> dict:
142
118
  """Return a dictionary of question attributes **except** for question_type.
143
119
 
144
- >>> from edsl import QuestionFreeText as Q
120
+ >>> from edsl.questions import QuestionFreeText as Q
145
121
  >>> Q.example().data
146
122
  {'question_name': 'how_are_you', 'question_text': 'How are you?'}
147
123
  """
@@ -186,7 +162,7 @@ class QuestionBase(
186
162
  def to_dict(self, add_edsl_version=True):
187
163
  """Convert the question to a dictionary that includes the question type (used in deserialization).
188
164
 
189
- >>> from edsl import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
165
+ >>> from edsl.questions import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
190
166
  {'question_name': 'how_are_you', 'question_text': 'How are you?', 'question_type': 'free_text'}
191
167
  """
192
168
  candidate_data = self.data.copy()
@@ -264,12 +240,10 @@ class QuestionBase(
264
240
  >>> m.execute_model_call("", "")
265
241
  {'message': [{'text': "Yo, what's up?"}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
266
242
  >>> 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?
243
+ Dataset([{'answer.how_are_you': ["Yo, what's up?"]}])
270
244
  """
271
245
  if model is None:
272
- from edsl import Model
246
+ from edsl.language_models.registry import Model
273
247
 
274
248
  model = Model()
275
249
  results = (
@@ -282,7 +256,7 @@ class QuestionBase(
282
256
  )
283
257
  )
284
258
  if show_answer:
285
- return results.select("answer.*").print()
259
+ return results.select("answer.*")
286
260
  else:
287
261
  return results
288
262
 
@@ -293,6 +267,7 @@ class QuestionBase(
293
267
  agent=None,
294
268
  disable_remote_cache: bool = False,
295
269
  disable_remote_inference: bool = False,
270
+ verbose: bool = False,
296
271
  **kwargs,
297
272
  ):
298
273
  """Call the question.
@@ -311,6 +286,7 @@ class QuestionBase(
311
286
  agent=agent,
312
287
  **kwargs,
313
288
  cache=False,
289
+ verbose=verbose,
314
290
  disable_remote_cache=disable_remote_cache,
315
291
  disable_remote_inference=disable_remote_inference,
316
292
  )
@@ -337,7 +313,7 @@ class QuestionBase(
337
313
  """Call the question asynchronously.
338
314
 
339
315
  >>> import asyncio
340
- >>> from edsl import QuestionFreeText as Q
316
+ >>> from edsl.questions import QuestionFreeText as Q
341
317
  >>> m = Q._get_test_model(canned_response = "Blue")
342
318
  >>> q = Q(question_name = "color", question_text = "What is your favorite color?")
343
319
  >>> async def test_run_async(): result = await q.run_async(model=m, disable_remote_inference = True); print(result)
@@ -356,27 +332,6 @@ class QuestionBase(
356
332
  else:
357
333
  return results
358
334
 
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
335
  def __getitem__(self, key: str) -> Any:
381
336
  """Get an attribute of the question so it can be treated like a dictionary.
382
337
 
@@ -384,7 +339,10 @@ class QuestionBase(
384
339
  >>> Q.example()['question_text']
385
340
  'How are you?'
386
341
  """
387
- return getattr(self, key)
342
+ try:
343
+ return getattr(self, key)
344
+ except TypeError:
345
+ raise KeyError(f"Question has no attribute {key} of type {type(key)}")
388
346
 
389
347
  def __repr__(self) -> str:
390
348
  """Return a string representation of the question. Should be able to be used to reconstruct the question.
@@ -414,9 +372,7 @@ class QuestionBase(
414
372
  False
415
373
 
416
374
  """
417
- if not isinstance(other, QuestionBase):
418
- return False
419
- return self.to_dict() == other.to_dict()
375
+ return hash(self) == hash(other)
420
376
 
421
377
  def __sub__(self, other) -> BaseDiff:
422
378
  """Return the difference between two objects.
@@ -433,35 +389,18 @@ class QuestionBase(
433
389
  def __add__(self, other_question_or_diff):
434
390
  """
435
391
  Compose two questions into a single question.
436
-
437
- TODO: Probably getting deprecated.
438
-
439
392
  """
440
393
  if isinstance(other_question_or_diff, BaseDiff) or isinstance(
441
394
  other_question_or_diff, BaseDiffCollection
442
395
  ):
443
396
  return other_question_or_diff.apply(self)
444
397
 
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
398
  def _translate_answer_code_to_answer(
457
399
  self, answer, scenario: Optional["Scenario"] = None
458
400
  ):
459
401
  """There is over-ridden by child classes that ask for codes."""
460
402
  return answer
461
403
 
462
- # endregion
463
-
464
- # region: Forward methods
465
404
  def add_question(self, other: QuestionBase) -> "Survey":
466
405
  """Add a question to this question by turning them into a survey with two questions.
467
406
 
@@ -471,10 +410,7 @@ class QuestionBase(
471
410
  >>> len(s.questions)
472
411
  2
473
412
  """
474
- from edsl.surveys.Survey import Survey
475
-
476
- s = Survey([self, other])
477
- return s
413
+ return self.to_survey().add_question(other)
478
414
 
479
415
  def to_survey(self) -> "Survey":
480
416
  """Turn a single question into a survey.
@@ -494,15 +430,6 @@ class QuestionBase(
494
430
  s = Survey([self])
495
431
  return s.by(*args)
496
432
 
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
433
  def human_readable(self) -> str:
507
434
  """Print the question in a human readable format.
508
435
 
@@ -529,97 +456,15 @@ class QuestionBase(
529
456
  width: Optional[int] = None,
530
457
  iframe=False,
531
458
  ):
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("")
459
+ from edsl.questions.HTMLQuestion import HTMLQuestion
565
460
 
566
- base_template = Template(base_template)
567
-
568
- context = {
569
- "scenario": scenario,
570
- "agent": agent,
571
- } | prior_answers_dict
572
-
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
461
+ return HTMLQuestion(self).html(
462
+ scenario, agent, answers, include_question_name, height, width, iframe
463
+ )
619
464
 
620
465
  @classmethod
621
466
  def example_model(cls):
622
- from edsl import Model
467
+ from edsl.language_models.registry import Model
623
468
 
624
469
  q = cls.example()
625
470
  m = Model("test", canned_response=cls._simulate_answer(q)["answer"])
@@ -21,7 +21,7 @@ class QuestionBaseGenMixin:
21
21
  def option_permutations(self) -> list[QuestionBase]:
22
22
  """Return a list of questions with all possible permutations of the options.
23
23
 
24
- >>> from edsl import QuestionMultipleChoice as Q
24
+ >>> from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice as Q
25
25
  >>> len(Q.example().option_permutations())
26
26
  24
27
27
  """
@@ -44,61 +44,65 @@ class QuestionBaseGenMixin:
44
44
 
45
45
  :param scenario_list: The list of scenarios to loop through.
46
46
 
47
- >>> from edsl import QuestionFreeText
48
- >>> from edsl import ScenarioList
47
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
48
+ >>> from edsl.scenarios.ScenarioList import ScenarioList
49
49
  >>> q = QuestionFreeText(question_text = "What are your thoughts on: {{ subject}}?", question_name = "base_{{subject}}")
50
50
  >>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
51
51
  3
52
-
53
52
  """
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
53
+ from edsl.questions.LoopProcessor import LoopProcessor
54
+
55
+ lp = LoopProcessor(self)
56
+ return lp.process_templates(scenario_list)
57
+
58
+ # from jinja2 import Environment
59
+ # from edsl.questions.QuestionBase import QuestionBase
60
+
61
+ # starting_name = self.question_name
62
+ # questions = []
63
+ # for index, scenario in enumerate(scenario_list):
64
+ # env = Environment()
65
+ # new_data = self.to_dict().copy()
66
+ # for key, value in [(k, v) for k, v in new_data.items() if v is not None]:
67
+ # if (
68
+ # isinstance(value, str) or isinstance(value, int)
69
+ # ) and key != "question_options":
70
+ # new_data[key] = env.from_string(value).render(scenario)
71
+ # elif isinstance(value, list):
72
+ # new_data[key] = [
73
+ # env.from_string(v).render(scenario) if isinstance(v, str) else v
74
+ # for v in value
75
+ # ]
76
+ # elif isinstance(value, dict):
77
+ # new_data[key] = {
78
+ # (
79
+ # env.from_string(k).render(scenario)
80
+ # if isinstance(k, str)
81
+ # else k
82
+ # ): (
83
+ # env.from_string(v).render(scenario)
84
+ # if isinstance(v, str)
85
+ # else v
86
+ # )
87
+ # for k, v in value.items()
88
+ # }
89
+ # elif key == "question_options" and isinstance(value, str):
90
+ # new_data[key] = value
91
+ # else:
92
+ # raise ValueError(
93
+ # f"Unexpected value type: {type(value)} for key '{key}'"
94
+ # )
95
+
96
+ # if new_data["question_name"] == starting_name:
97
+ # new_data["question_name"] = new_data["question_name"] + f"_{index}"
98
+
99
+ # questions.append(QuestionBase.from_dict(new_data))
100
+ # return questions
97
101
 
98
102
  def render(self, replacement_dict: dict) -> "QuestionBase":
99
103
  """Render the question components as jinja2 templates with the replacement dictionary."""
100
104
  from jinja2 import Environment
101
- from edsl import Scenario
105
+ from edsl.scenarios.Scenario import Scenario
102
106
 
103
107
  strings_only_replacement_dict = {
104
108
  k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
@@ -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.registry 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
@@ -16,7 +16,7 @@ from pydantic import field_validator
16
16
  from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
17
17
  from edsl.questions.ResponseValidatorABC 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
@@ -8,7 +8,7 @@ from edsl.questions.descriptors import AnswerTemplateDescriptor
8
8
 
9
9
  from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
10
10
  from edsl.questions.ResponseValidatorABC import BaseResponse
11
- from edsl.exceptions import QuestionAnswerValidationError
11
+ from edsl.exceptions.questions import QuestionAnswerValidationError
12
12
  from edsl.questions.decorators import inject_exception
13
13
 
14
14
  from typing import Dict, Any
@@ -7,13 +7,12 @@ from pydantic import field_validator
7
7
  from edsl.questions.QuestionBase import QuestionBase
8
8
  from edsl.questions.ResponseValidatorABC 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,6 +1,6 @@
1
1
  from __future__ import annotations
2
- import random
3
- import textwrap
2
+ import json
3
+
4
4
  from typing import Any, Optional, Union
5
5
  from edsl.questions.QuestionBase import QuestionBase
6
6
  from edsl.questions.descriptors import IntegerOrNoneDescriptor
@@ -10,9 +10,7 @@ from pydantic import field_validator, Field
10
10
  from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
11
11
  from edsl.questions.ResponseValidatorABC import BaseResponse
12
12
 
13
- from edsl.exceptions import QuestionAnswerValidationError
14
- import textwrap
15
- import json
13
+ from edsl.exceptions.questions import QuestionAnswerValidationError
16
14
 
17
15
  from json_repair import repair_json
18
16