edsl 0.1.39__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 (85) hide show
  1. edsl/Base.py +0 -28
  2. edsl/__init__.py +1 -1
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +17 -9
  5. edsl/agents/Invigilator.py +14 -13
  6. edsl/agents/InvigilatorBase.py +1 -4
  7. edsl/agents/PromptConstructor.py +22 -42
  8. edsl/agents/QuestionInstructionPromptBuilder.py +1 -1
  9. edsl/auto/AutoStudy.py +5 -18
  10. edsl/auto/StageBase.py +40 -53
  11. edsl/auto/StageQuestions.py +1 -2
  12. edsl/auto/utilities.py +6 -0
  13. edsl/coop/coop.py +5 -21
  14. edsl/data/Cache.py +18 -29
  15. edsl/data/CacheHandler.py +2 -0
  16. edsl/data/RemoteCacheSync.py +46 -154
  17. edsl/enums.py +0 -7
  18. edsl/inference_services/AnthropicService.py +16 -38
  19. edsl/inference_services/AvailableModelFetcher.py +1 -7
  20. edsl/inference_services/GoogleService.py +1 -5
  21. edsl/inference_services/InferenceServicesCollection.py +2 -18
  22. edsl/inference_services/OpenAIService.py +31 -46
  23. edsl/inference_services/TestService.py +3 -1
  24. edsl/inference_services/TogetherAIService.py +3 -5
  25. edsl/inference_services/data_structures.py +2 -74
  26. edsl/jobs/AnswerQuestionFunctionConstructor.py +113 -148
  27. edsl/jobs/FetchInvigilator.py +3 -10
  28. edsl/jobs/InterviewsConstructor.py +4 -6
  29. edsl/jobs/Jobs.py +233 -299
  30. edsl/jobs/JobsChecks.py +2 -2
  31. edsl/jobs/JobsPrompts.py +1 -1
  32. edsl/jobs/JobsRemoteInferenceHandler.py +136 -160
  33. edsl/jobs/interviews/Interview.py +42 -80
  34. edsl/jobs/runners/JobsRunnerAsyncio.py +358 -88
  35. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  36. edsl/jobs/tasks/TaskHistory.py +3 -24
  37. edsl/language_models/LanguageModel.py +4 -59
  38. edsl/language_models/ModelList.py +8 -19
  39. edsl/language_models/__init__.py +1 -1
  40. edsl/language_models/registry.py +180 -0
  41. edsl/language_models/repair.py +1 -1
  42. edsl/questions/QuestionBase.py +26 -35
  43. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +49 -52
  44. edsl/questions/QuestionBasePromptsMixin.py +1 -1
  45. edsl/questions/QuestionBudget.py +1 -1
  46. edsl/questions/QuestionCheckBox.py +2 -2
  47. edsl/questions/QuestionExtract.py +7 -5
  48. edsl/questions/QuestionFreeText.py +1 -1
  49. edsl/questions/QuestionList.py +15 -9
  50. edsl/questions/QuestionMatrix.py +1 -1
  51. edsl/questions/QuestionMultipleChoice.py +1 -1
  52. edsl/questions/QuestionNumerical.py +1 -1
  53. edsl/questions/QuestionRank.py +1 -1
  54. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +18 -6
  55. edsl/questions/{response_validator_factory.py → ResponseValidatorFactory.py} +1 -7
  56. edsl/questions/SimpleAskMixin.py +1 -1
  57. edsl/questions/__init__.py +1 -1
  58. edsl/results/DatasetExportMixin.py +119 -60
  59. edsl/results/Result.py +3 -109
  60. edsl/results/Results.py +39 -50
  61. edsl/scenarios/FileStore.py +0 -32
  62. edsl/scenarios/ScenarioList.py +7 -35
  63. edsl/scenarios/handlers/csv.py +0 -11
  64. edsl/surveys/Survey.py +20 -71
  65. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +1 -1
  66. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/RECORD +78 -84
  67. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +1 -1
  68. edsl/jobs/async_interview_runner.py +0 -138
  69. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  70. edsl/jobs/data_structures.py +0 -120
  71. edsl/jobs/results_exceptions_handler.py +0 -98
  72. edsl/language_models/model.py +0 -256
  73. edsl/questions/data_structures.py +0 -20
  74. edsl/results/file_exports.py +0 -252
  75. /edsl/agents/{question_option_processor.py → QuestionOptionProcessor.py} +0 -0
  76. /edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +0 -0
  77. /edsl/questions/{loop_processor.py → LoopProcessor.py} +0 -0
  78. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  79. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  80. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  81. /edsl/results/{results_selector.py → Selector.py} +0 -0
  82. /edsl/scenarios/{directory_scanner.py → DirectoryScanner.py} +0 -0
  83. /edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +0 -0
  84. /edsl/scenarios/{scenario_selector.py → ScenarioSelector.py} +0 -0
  85. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,180 @@
1
+ import textwrap
2
+ from random import random
3
+ from edsl.config import CONFIG
4
+ from functools import lru_cache
5
+ from edsl.utilities.PrettyList import PrettyList
6
+ from typing import Optional
7
+
8
+
9
+ def get_model_class(model_name, registry=None):
10
+ from edsl.inference_services.registry import default
11
+
12
+ registry = registry or default
13
+ factory = registry.create_model_factory(model_name)
14
+ return factory
15
+
16
+
17
+ class Meta(type):
18
+ def __repr__(cls):
19
+ return textwrap.dedent(
20
+ f"""\
21
+ Available models: {cls.available()}
22
+
23
+ To create an instance, you can do:
24
+ >>> m = Model('gpt-4-1106-preview', temperature=0.5, ...)
25
+
26
+ To get the default model, you can leave out the model name.
27
+ To see the available models, you can do:
28
+ >>> Model.available()
29
+ """
30
+ )
31
+
32
+
33
+ class Model(metaclass=Meta):
34
+ default_model = CONFIG.get("EDSL_DEFAULT_MODEL")
35
+
36
+ def __new__(
37
+ cls, model_name=None, registry=None, service_name=None, *args, **kwargs
38
+ ):
39
+ # Map index to the respective subclass
40
+ if model_name is None:
41
+ model_name = (
42
+ cls.default_model
43
+ ) # when model_name is None, use the default model, set in the config file
44
+ from edsl.inference_services.registry import default
45
+
46
+ registry = registry or default
47
+
48
+ if isinstance(model_name, int): # can refer to a model by index
49
+ model_name = cls.available(name_only=True)[model_name]
50
+
51
+ factory = registry.create_model_factory(model_name, service_name=service_name)
52
+ return factory(*args, **kwargs)
53
+
54
+ @classmethod
55
+ def add_model(cls, service_name, model_name):
56
+ from edsl.inference_services.registry import default
57
+
58
+ registry = default
59
+ registry.add_model(service_name, model_name)
60
+
61
+ @classmethod
62
+ def service_classes(cls, registry=None):
63
+ from edsl.inference_services.registry import default
64
+
65
+ registry = registry or default
66
+ return [r for r in registry.services]
67
+
68
+ @classmethod
69
+ def services(cls, registry=None):
70
+ from edsl.inference_services.registry import default
71
+
72
+ registry = registry or default
73
+ return PrettyList(
74
+ [r._inference_service_ for r in registry.services], columns=["Service Name"]
75
+ )
76
+
77
+ @classmethod
78
+ def key_info(cls):
79
+ from edsl.language_models.key_management import KeyLookupCollection
80
+ from edsl.scenarios import Scenario, ScenarioList
81
+
82
+ klc = KeyLookupCollection()
83
+ klc.add_key_lookup(fetch_order=None)
84
+ sl = ScenarioList()
85
+ for service, entry in list(klc.data.values())[0].items():
86
+ sl.append(Scenario({"service": service} | entry.to_dict()))
87
+ return sl.to_dataset()
88
+
89
+ @classmethod
90
+ def available(
91
+ cls,
92
+ search_term: str = None,
93
+ name_only: bool = False,
94
+ registry=None,
95
+ service: Optional[str] = None,
96
+ ):
97
+ from edsl.inference_services.registry import default
98
+
99
+ registry = registry or default
100
+ # full_list = registry.available()
101
+
102
+ if service is not None:
103
+ if service not in cls.services(registry=registry):
104
+ raise ValueError(f"Service {service} not found in available services.")
105
+
106
+ # import time
107
+ # start = time.time()
108
+ full_list = registry.available(service=service)
109
+ # end = time.time()
110
+ # print(f"Time taken to get available models: {end-start}")
111
+
112
+ if search_term is None:
113
+ if name_only:
114
+ return PrettyList(
115
+ [m.model_name for m in full_list],
116
+ columns=["Model Name"],
117
+ )
118
+ else:
119
+ return PrettyList(
120
+ [[m.model_name, m.service_name] for m in full_list],
121
+ columns=["Model Name", "Service Name"],
122
+ )
123
+ else:
124
+ filtered_results = [
125
+ m
126
+ for m in full_list
127
+ if search_term in m.model_name or search_term in m.service_name
128
+ ]
129
+ if name_only:
130
+ return PrettyList(
131
+ [m.model_name for m in filtered_results],
132
+ columns=["Model Name"],
133
+ )
134
+ else:
135
+ return PrettyList(
136
+ [[m.model_name, m.service_name] for m in full_list],
137
+ columns=["Model Name", "Service Name"],
138
+ )
139
+
140
+ @classmethod
141
+ def check_models(cls, verbose=False):
142
+ print("Checking all available models...\n")
143
+ for model in cls.available(name_only=True):
144
+ print(f"Now checking: {model}")
145
+ try:
146
+ m = cls(model)
147
+ except Exception as e:
148
+ print(f"Error creating instance of {model}: {e}")
149
+ continue
150
+ try:
151
+ results = m.hello(verbose)
152
+ if verbose:
153
+ print(f"Results from model call: {results}")
154
+ except Exception as e:
155
+ print(f"Error calling 'hello' on {model}: {e}")
156
+ continue
157
+ print("OK!")
158
+ print("\n")
159
+
160
+ @classmethod
161
+ def example(cls, randomize: bool = False) -> "Model":
162
+ """
163
+ Returns an example Model instance.
164
+
165
+ :param randomize: If True, the temperature is set to a random decimal between 0 and 1.
166
+ """
167
+ temperature = 0.5 if not randomize else round(random(), 2)
168
+ model_name = cls.default_model
169
+ return cls(model_name, temperature=temperature)
170
+
171
+
172
+ if __name__ == "__main__":
173
+ import doctest
174
+
175
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
176
+
177
+ available = Model.available()
178
+ m = Model("gpt-4-1106-preview")
179
+ results = m.execute_model_call("Hello world")
180
+ print(results)
@@ -32,7 +32,7 @@ async def async_repair(
32
32
  else:
33
33
  return valid_dict, success
34
34
 
35
- from edsl.language_models.model import Model
35
+ from edsl.language_models.registry import Model
36
36
 
37
37
  m = Model()
38
38
 
@@ -2,31 +2,23 @@
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, TYPE_CHECKING
5
+ from typing import Any, Type, Optional, List, Callable, Union, TypedDict
6
6
 
7
7
  from edsl.exceptions.questions import (
8
8
  QuestionSerializationError,
9
9
  )
10
10
  from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
11
11
 
12
- from edsl.questions.answer_validator_mixin import AnswerValidatorMixin
13
- from edsl.questions.register_questions_meta import RegisterQuestionsMeta
12
+ from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
13
+ from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
14
14
  from edsl.Base import PersistenceMixin, RepresentationMixin
15
15
  from edsl.BaseDiff import BaseDiff, BaseDiffCollection
16
16
 
17
17
  from edsl.questions.SimpleAskMixin import SimpleAskMixin
18
18
  from edsl.questions.QuestionBasePromptsMixin import QuestionBasePromptsMixin
19
- from edsl.questions.question_base_gen_mixin import QuestionBaseGenMixin
19
+ from edsl.questions.QuestionBaseGenMixin import QuestionBaseGenMixin
20
20
  from edsl.utilities.remove_edsl_version import remove_edsl_version
21
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
29
-
30
22
 
31
23
  class QuestionBase(
32
24
  PersistenceMixin,
@@ -57,17 +49,13 @@ class QuestionBase(
57
49
  _question_presentation = None
58
50
 
59
51
  @property
60
- def response_validator(self) -> "ResponseValidatorABC":
52
+ def response_validator(self) -> "ResponseValidatorBase":
61
53
  """Return the response validator."""
62
- from edsl.questions.response_validator_factory import ResponseValidatorFactory
54
+ from edsl.questions.ResponseValidatorFactory import ResponseValidatorFactory
63
55
 
64
56
  rvf = ResponseValidatorFactory(self)
65
57
  return rvf.response_validator
66
58
 
67
- def duplicate(self):
68
- """Return a duplicate of the question."""
69
- return self.from_dict(self.to_dict())
70
-
71
59
  @property
72
60
  def fake_data_factory(self):
73
61
  """Return the fake data factory."""
@@ -171,7 +159,7 @@ class QuestionBase(
171
159
 
172
160
  return candidate_data
173
161
 
174
- def to_dict(self, add_edsl_version: bool = True):
162
+ def to_dict(self, add_edsl_version=True):
175
163
  """Convert the question to a dictionary that includes the question type (used in deserialization).
176
164
 
177
165
  >>> from edsl.questions import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
@@ -225,6 +213,9 @@ class QuestionBase(
225
213
 
226
214
  return question_class(**local_data)
227
215
 
216
+ # endregion
217
+
218
+ # region: Running methods
228
219
  @classmethod
229
220
  def _get_test_model(self, canned_response: Optional[str] = None) -> "LanguageModel":
230
221
  """Get a test model for the question."""
@@ -252,7 +243,7 @@ class QuestionBase(
252
243
  Dataset([{'answer.how_are_you': ["Yo, what's up?"]}])
253
244
  """
254
245
  if model is None:
255
- from edsl.language_models.model import Model
246
+ from edsl.language_models.registry import Model
256
247
 
257
248
  model = Model()
258
249
  results = (
@@ -271,22 +262,21 @@ class QuestionBase(
271
262
 
272
263
  def __call__(
273
264
  self,
274
- just_answer: bool = True,
275
- model: Optional["LanguageModel"] = None,
276
- agent: Optional["Agent"] = None,
265
+ just_answer=True,
266
+ model=None,
267
+ agent=None,
277
268
  disable_remote_cache: bool = False,
278
269
  disable_remote_inference: bool = False,
279
270
  verbose: bool = False,
280
271
  **kwargs,
281
- ) -> Union[Any, "Results"]:
272
+ ):
282
273
  """Call the question.
283
274
 
284
275
 
285
276
  >>> from edsl import QuestionFreeText as Q
286
- >>> from edsl import Model
287
- >>> m = Model("test", canned_response = "Yo, what's up?")
277
+ >>> m = Q._get_test_model(canned_response = "Yo, what's up?")
288
278
  >>> q = Q(question_name = "color", question_text = "What is your favorite color?")
289
- >>> q(model = m, disable_remote_cache = True, disable_remote_inference = True, cache = False)
279
+ >>> q(model = m, disable_remote_cache = True, disable_remote_inference = True)
290
280
  "Yo, what's up?"
291
281
 
292
282
  """
@@ -295,6 +285,7 @@ class QuestionBase(
295
285
  model=model,
296
286
  agent=agent,
297
287
  **kwargs,
288
+ cache=False,
298
289
  verbose=verbose,
299
290
  disable_remote_cache=disable_remote_cache,
300
291
  disable_remote_inference=disable_remote_inference,
@@ -306,16 +297,15 @@ class QuestionBase(
306
297
 
307
298
  def run(self, *args, **kwargs) -> "Results":
308
299
  """Turn a single question into a survey and runs it."""
309
- return self.to_survey().run(*args, **kwargs)
300
+ from edsl.surveys.Survey import Survey
310
301
 
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)
302
+ s = self.to_survey()
303
+ return s.run(*args, **kwargs)
314
304
 
315
305
  async def run_async(
316
306
  self,
317
307
  just_answer: bool = True,
318
- model: Optional["LanguageModel"] = None,
308
+ model: Optional["Model"] = None,
319
309
  agent: Optional["Agent"] = None,
320
310
  disable_remote_inference: bool = False,
321
311
  **kwargs,
@@ -326,7 +316,7 @@ class QuestionBase(
326
316
  >>> from edsl.questions import QuestionFreeText as Q
327
317
  >>> m = Q._get_test_model(canned_response = "Blue")
328
318
  >>> q = Q(question_name = "color", question_text = "What is your favorite color?")
329
- >>> async def test_run_async(): result = await q.run_async(model=m, disable_remote_inference = True, disable_remote_cache = True); print(result)
319
+ >>> async def test_run_async(): result = await q.run_async(model=m, disable_remote_inference = True); print(result)
330
320
  >>> asyncio.run(test_run_async())
331
321
  Blue
332
322
  """
@@ -430,7 +420,8 @@ class QuestionBase(
430
420
  """
431
421
  from edsl.surveys.Survey import Survey
432
422
 
433
- return Survey([self])
423
+ s = Survey([self])
424
+ return s
434
425
 
435
426
  def by(self, *args) -> "Jobs":
436
427
  """Turn a single question into a survey and then a Job."""
@@ -473,7 +464,7 @@ class QuestionBase(
473
464
 
474
465
  @classmethod
475
466
  def example_model(cls):
476
- from edsl.language_models.model import Model
467
+ from edsl.language_models.registry import Model
477
468
 
478
469
  q = cls.example()
479
470
  m = Model("test", canned_response=cls._simulate_answer(q)["answer"])
@@ -1,16 +1,11 @@
1
1
  from __future__ import annotations
2
2
  import copy
3
3
  import itertools
4
- from typing import Optional, List, Callable, Type, TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- from edsl.questions.QuestionBase import QuestionBase
8
- from edsl.scenarios.ScenarioList import ScenarioList
4
+ from typing import Optional, List, Callable, Type
5
+ from typing import TypeVar
9
6
 
10
7
 
11
8
  class QuestionBaseGenMixin:
12
- """Mixin for QuestionBase."""
13
-
14
9
  def copy(self) -> QuestionBase:
15
10
  """Return a deep copy of the question.
16
11
 
@@ -44,31 +39,6 @@ class QuestionBaseGenMixin:
44
39
  questions.append(question)
45
40
  return questions
46
41
 
47
- def draw(self) -> "QuestionBase":
48
- """Return a new question with a randomly selected permutation of the options.
49
-
50
- If the question has no options, returns a copy of the original question.
51
-
52
- >>> from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice as Q
53
- >>> q = Q.example()
54
- >>> drawn = q.draw()
55
- >>> len(drawn.question_options) == len(q.question_options)
56
- True
57
- >>> q is drawn
58
- False
59
- """
60
-
61
- if not hasattr(self, "question_options"):
62
- return copy.deepcopy(self)
63
-
64
- import random
65
-
66
- question = copy.deepcopy(self)
67
- question.question_options = list(
68
- random.sample(self.question_options, len(self.question_options))
69
- )
70
- return question
71
-
72
42
  def loop(self, scenario_list: ScenarioList) -> List[QuestionBase]:
73
43
  """Return a list of questions with the question name modified for each scenario.
74
44
 
@@ -80,22 +50,57 @@ class QuestionBaseGenMixin:
80
50
  >>> len(q.loop(ScenarioList.from_list("subject", ["Math", "Economics", "Chemistry"])))
81
51
  3
82
52
  """
83
- from edsl.questions.loop_processor import LoopProcessor
53
+ from edsl.questions.LoopProcessor import LoopProcessor
84
54
 
85
55
  lp = LoopProcessor(self)
86
56
  return lp.process_templates(scenario_list)
87
57
 
88
- def render(self, replacement_dict: dict) -> "QuestionBase":
89
- """Render the question components as jinja2 templates with the replacement dictionary.
90
-
91
- :param replacement_dict: The dictionary of values to replace in the question components.
92
-
93
- >>> from edsl.questions.QuestionFreeText import QuestionFreeText
94
- >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite {{ thing }}?")
95
- >>> q.render({"thing": "color"})
96
- Question('free_text', question_name = \"""color\""", question_text = \"""What is your favorite color?\""")
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":
103
+ """Render the question components as jinja2 templates with the replacement dictionary."""
99
104
  from jinja2 import Environment
100
105
  from edsl.scenarios.Scenario import Scenario
101
106
 
@@ -122,23 +127,15 @@ class QuestionBaseGenMixin:
122
127
 
123
128
  return self.apply_function(render_string)
124
129
 
125
- def apply_function(
126
- self, func: Callable, exclude_components: List[str] = None
127
- ) -> QuestionBase:
130
+ def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
128
131
  """Apply a function to the question parts
129
132
 
130
- :param func: The function to apply to the question parts.
131
- :param exclude_components: The components to exclude from the function application.
132
-
133
133
  >>> from edsl.questions import QuestionFreeText
134
134
  >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
135
135
  >>> shouting = lambda x: x.upper()
136
136
  >>> q.apply_function(shouting)
137
137
  Question('free_text', question_name = \"""color\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
138
138
 
139
- >>> q.apply_function(shouting, exclude_components = ["question_type"])
140
- Question('free_text', question_name = \"""COLOR\""", question_text = \"""WHAT IS YOUR FAVORITE COLOR?\""")
141
-
142
139
  """
143
140
  from edsl.questions.QuestionBase import QuestionBase
144
141
 
@@ -69,7 +69,7 @@ class QuestionBasePromptsMixin:
69
69
  >>> q.get_instructions(model = "gpt3")
70
70
  Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
71
71
  """
72
- from edsl.language_models.model import Model
72
+ from edsl.language_models.registry import Model
73
73
 
74
74
  if not hasattr(self, "_model_instructions"):
75
75
  self._model_instructions = {}
@@ -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.response_validator_abc import ResponseValidatorABC
8
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
9
9
 
10
10
 
11
11
  class BudgewResponseValidator(ResponseValidatorABC):
@@ -13,8 +13,8 @@ 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.response_validator_abc import ResponseValidatorABC
17
- from edsl.questions.data_structures import BaseResponse
16
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
17
+ from edsl.questions.ResponseValidatorABC import BaseResponse
18
18
 
19
19
  from edsl.exceptions.questions import QuestionAnswerValidationError
20
20
 
@@ -6,8 +6,9 @@ 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.response_validator_abc import ResponseValidatorABC
10
- from edsl.questions.data_structures import BaseResponse
9
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
10
+ from edsl.questions.ResponseValidatorABC import BaseResponse
11
+ from edsl.exceptions.questions import QuestionAnswerValidationError
11
12
  from edsl.questions.decorators import inject_exception
12
13
 
13
14
  from typing import Dict, Any
@@ -56,7 +57,7 @@ def dict_to_pydantic_model(input_dict: Dict[str, Any]) -> Any:
56
57
  DynamicModel = create_model("DynamicModel", **field_definitions)
57
58
 
58
59
  class AnswerModel(BaseResponse):
59
- answer: "DynamicModel"
60
+ answer: DynamicModel
60
61
  generated_tokens: Optional[str] = None
61
62
  comment: Optional[str] = None
62
63
 
@@ -112,8 +113,6 @@ class QuestionExtract(QuestionBase):
112
113
  :param question_name: The name of the question.
113
114
  :param question_text: The text of the question.
114
115
  :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.
117
116
  """
118
117
  self.question_name = question_name
119
118
  self.question_text = question_text
@@ -143,6 +142,9 @@ class QuestionExtract(QuestionBase):
143
142
  )
144
143
  return question_html_content
145
144
 
145
+ ################
146
+ # Helpful methods
147
+ ################
146
148
  @classmethod
147
149
  @inject_exception
148
150
  def example(cls) -> QuestionExtract:
@@ -5,7 +5,7 @@ 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.response_validator_abc import ResponseValidatorABC
8
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
9
9
 
10
10
  from edsl.exceptions.questions import QuestionAnswerValidationError
11
11
  from edsl.questions.decorators import inject_exception
@@ -1,18 +1,21 @@
1
1
  from __future__ import annotations
2
2
  import json
3
- from typing import Any, Optional, Union
4
-
5
- from pydantic import Field
6
- from json_repair import repair_json
7
3
 
8
- from edsl.exceptions.questions import QuestionAnswerValidationError
4
+ from typing import Any, Optional, Union
9
5
  from edsl.questions.QuestionBase import QuestionBase
10
6
  from edsl.questions.descriptors import IntegerOrNoneDescriptor
11
7
  from edsl.questions.decorators import inject_exception
12
- from edsl.questions.response_validator_abc import ResponseValidatorABC
13
8
 
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.questions import QuestionAnswerValidationError
14
+
15
+ from json_repair import repair_json
14
16
 
15
- def convert_string(s: str) -> Union[float, int, str, dict]:
17
+
18
+ def convert_string(s):
16
19
  """Convert a string to a more appropriate type if possible.
17
20
 
18
21
  >>> convert_string("3.14")
@@ -55,7 +58,7 @@ def convert_string(s: str) -> Union[float, int, str, dict]:
55
58
  return s
56
59
 
57
60
 
58
- def create_model(max_list_items: int, permissive: bool) -> "ListResponse":
61
+ def create_model(max_list_items: int, permissive):
59
62
  from pydantic import BaseModel
60
63
 
61
64
  if permissive or max_list_items is None:
@@ -130,8 +133,8 @@ class QuestionList(QuestionBase):
130
133
  self,
131
134
  question_name: str,
132
135
  question_text: str,
133
- include_comment: bool = True,
134
136
  max_list_items: Optional[int] = None,
137
+ include_comment: bool = True,
135
138
  answering_instructions: Optional[str] = None,
136
139
  question_presentation: Optional[str] = None,
137
140
  permissive: bool = False,
@@ -181,6 +184,9 @@ class QuestionList(QuestionBase):
181
184
  ).render(question_name=self.question_name)
182
185
  return question_html_content
183
186
 
187
+ ################
188
+ # Helpful methods
189
+ ################
184
190
  @classmethod
185
191
  @inject_exception
186
192
  def example(
@@ -10,7 +10,7 @@ from edsl.questions.descriptors import (
10
10
  OptionLabelDescriptor,
11
11
  QuestionTextDescriptor,
12
12
  )
13
- from edsl.questions.response_validator_abc import ResponseValidatorABC
13
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
14
14
  from edsl.questions.decorators import inject_exception
15
15
  from edsl.exceptions.questions import (
16
16
  QuestionAnswerValidationError,
@@ -8,7 +8,7 @@ from edsl.scenarios.Scenario import Scenario
8
8
  from edsl.questions.QuestionBase import QuestionBase
9
9
  from edsl.questions.descriptors import QuestionOptionsDescriptor
10
10
  from edsl.questions.decorators import inject_exception
11
- from edsl.questions.response_validator_abc import ResponseValidatorABC
11
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
12
12
 
13
13
 
14
14
  def create_response_model(choices: List[str], permissive: bool = False):
@@ -9,7 +9,7 @@ from edsl.exceptions.questions import QuestionAnswerValidationError
9
9
  from edsl.questions.QuestionBase import QuestionBase
10
10
  from edsl.questions.descriptors import NumericalOrNoneDescriptor
11
11
  from edsl.questions.decorators import inject_exception
12
- from edsl.questions.response_validator_abc import ResponseValidatorABC
12
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
13
13
 
14
14
 
15
15
  def create_numeric_response(