edsl 0.1.29__py3-none-any.whl → 0.1.29.dev1__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 (76) hide show
  1. edsl/Base.py +18 -18
  2. edsl/__init__.py +24 -24
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +41 -77
  5. edsl/agents/AgentList.py +6 -35
  6. edsl/agents/Invigilator.py +1 -19
  7. edsl/agents/InvigilatorBase.py +10 -15
  8. edsl/agents/PromptConstructionMixin.py +100 -342
  9. edsl/agents/descriptors.py +1 -2
  10. edsl/config.py +1 -2
  11. edsl/conjure/InputData.py +8 -39
  12. edsl/coop/coop.py +150 -187
  13. edsl/coop/utils.py +75 -43
  14. edsl/data/Cache.py +5 -19
  15. edsl/data/SQLiteDict.py +3 -11
  16. edsl/jobs/Answers.py +1 -15
  17. edsl/jobs/Jobs.py +46 -90
  18. edsl/jobs/buckets/ModelBuckets.py +2 -4
  19. edsl/jobs/buckets/TokenBucket.py +2 -1
  20. edsl/jobs/interviews/Interview.py +9 -3
  21. edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
  22. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +10 -15
  23. edsl/jobs/runners/JobsRunnerAsyncio.py +25 -21
  24. edsl/jobs/tasks/TaskHistory.py +3 -4
  25. edsl/language_models/LanguageModel.py +11 -5
  26. edsl/language_models/ModelList.py +3 -3
  27. edsl/language_models/repair.py +7 -8
  28. edsl/notebooks/Notebook.py +3 -40
  29. edsl/prompts/Prompt.py +19 -31
  30. edsl/questions/QuestionBase.py +13 -38
  31. edsl/questions/QuestionBudget.py +6 -5
  32. edsl/questions/QuestionCheckBox.py +3 -7
  33. edsl/questions/QuestionExtract.py +3 -5
  34. edsl/questions/QuestionFreeText.py +3 -3
  35. edsl/questions/QuestionFunctional.py +3 -0
  36. edsl/questions/QuestionList.py +4 -3
  37. edsl/questions/QuestionMultipleChoice.py +8 -16
  38. edsl/questions/QuestionNumerical.py +3 -4
  39. edsl/questions/QuestionRank.py +3 -5
  40. edsl/questions/__init__.py +3 -4
  41. edsl/questions/descriptors.py +2 -4
  42. edsl/questions/question_registry.py +31 -20
  43. edsl/questions/settings.py +1 -1
  44. edsl/results/Dataset.py +0 -31
  45. edsl/results/Result.py +74 -22
  46. edsl/results/Results.py +47 -97
  47. edsl/results/ResultsDBMixin.py +3 -7
  48. edsl/results/ResultsExportMixin.py +537 -22
  49. edsl/results/ResultsGGMixin.py +3 -3
  50. edsl/results/ResultsToolsMixin.py +5 -5
  51. edsl/scenarios/Scenario.py +6 -5
  52. edsl/scenarios/ScenarioList.py +11 -34
  53. edsl/scenarios/ScenarioListPdfMixin.py +1 -2
  54. edsl/scenarios/__init__.py +0 -1
  55. edsl/study/ObjectEntry.py +13 -89
  56. edsl/study/ProofOfWork.py +2 -5
  57. edsl/study/SnapShot.py +8 -4
  58. edsl/study/Study.py +14 -21
  59. edsl/study/__init__.py +0 -2
  60. edsl/surveys/MemoryPlan.py +4 -11
  61. edsl/surveys/Survey.py +7 -46
  62. edsl/surveys/SurveyExportMixin.py +2 -4
  63. edsl/surveys/SurveyFlowVisualizationMixin.py +4 -6
  64. edsl/tools/plotting.py +2 -4
  65. edsl/utilities/__init__.py +21 -21
  66. edsl/utilities/interface.py +45 -66
  67. edsl/utilities/utilities.py +13 -11
  68. {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/METADATA +10 -11
  69. {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/RECORD +72 -75
  70. edsl-0.1.29.dev1.dist-info/entry_points.txt +3 -0
  71. edsl/base/Base.py +0 -289
  72. edsl/results/DatasetExportMixin.py +0 -493
  73. edsl/scenarios/FileStore.py +0 -140
  74. edsl/scenarios/ScenarioListExportMixin.py +0 -32
  75. {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/LICENSE +0 -0
  76. {edsl-0.1.29.dist-info → edsl-0.1.29.dev1.dist-info}/WHEEL +0 -0
@@ -1,17 +1,29 @@
1
1
  from __future__ import annotations
2
2
  import time
3
3
  import asyncio
4
- import time
4
+ import textwrap
5
5
  from contextlib import contextmanager
6
6
 
7
7
  from typing import Coroutine, List, AsyncGenerator, Optional, Union
8
8
 
9
+ from rich.live import Live
10
+ from rich.console import Console
11
+
9
12
  from edsl import shared_globals
13
+ from edsl.results import Results, Result
14
+
10
15
  from edsl.jobs.interviews.Interview import Interview
16
+ from edsl.utilities.decorators import jupyter_nb_handler
17
+
18
+ # from edsl.jobs.Jobs import Jobs
11
19
  from edsl.jobs.runners.JobsRunnerStatusMixin import JobsRunnerStatusMixin
20
+ from edsl.language_models import LanguageModel
21
+ from edsl.data.Cache import Cache
22
+
12
23
  from edsl.jobs.tasks.TaskHistory import TaskHistory
13
24
  from edsl.jobs.buckets.BucketCollection import BucketCollection
14
- from edsl.utilities.decorators import jupyter_nb_handler
25
+
26
+ import time
15
27
 
16
28
 
17
29
  class JobsRunnerAsyncio(JobsRunnerStatusMixin):
@@ -30,13 +42,13 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
30
42
 
31
43
  async def run_async_generator(
32
44
  self,
33
- cache: "Cache",
45
+ cache: Cache,
34
46
  n: int = 1,
35
47
  debug: bool = False,
36
48
  stop_on_exception: bool = False,
37
49
  sidecar_model: "LanguageModel" = None,
38
50
  total_interviews: Optional[List["Interview"]] = None,
39
- ) -> AsyncGenerator["Result", None]:
51
+ ) -> AsyncGenerator[Result, None]:
40
52
  """Creates the tasks, runs them asynchronously, and returns the results as a Results object.
41
53
 
42
54
  Completed tasks are yielded as they are completed.
@@ -143,21 +155,19 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
143
155
 
144
156
  prompt_dictionary = {}
145
157
  for answer_key_name in answer_key_names:
146
- prompt_dictionary[answer_key_name + "_user_prompt"] = (
147
- question_name_to_prompts[answer_key_name]["user_prompt"]
148
- )
149
- prompt_dictionary[answer_key_name + "_system_prompt"] = (
150
- question_name_to_prompts[answer_key_name]["system_prompt"]
151
- )
158
+ prompt_dictionary[
159
+ answer_key_name + "_user_prompt"
160
+ ] = question_name_to_prompts[answer_key_name]["user_prompt"]
161
+ prompt_dictionary[
162
+ answer_key_name + "_system_prompt"
163
+ ] = question_name_to_prompts[answer_key_name]["system_prompt"]
152
164
 
153
165
  raw_model_results_dictionary = {}
154
166
  for result in valid_results:
155
167
  question_name = result["question_name"]
156
- raw_model_results_dictionary[question_name + "_raw_model_response"] = (
157
- result["raw_model_response"]
158
- )
159
-
160
- from edsl.results.Result import Result
168
+ raw_model_results_dictionary[
169
+ question_name + "_raw_model_response"
170
+ ] = result["raw_model_response"]
161
171
 
162
172
  result = Result(
163
173
  agent=interview.agent,
@@ -187,8 +197,6 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
187
197
  print_exceptions: bool = True,
188
198
  ) -> "Coroutine":
189
199
  """Runs a collection of interviews, handling both async and sync contexts."""
190
- from rich.console import Console
191
-
192
200
  console = Console()
193
201
  self.results = []
194
202
  self.start_time = time.monotonic()
@@ -196,8 +204,6 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
196
204
  self.cache = cache
197
205
  self.sidecar_model = sidecar_model
198
206
 
199
- from edsl.results.Results import Results
200
-
201
207
  if not progress_bar:
202
208
  # print("Running without progress bar")
203
209
  with cache as c:
@@ -219,8 +225,6 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
219
225
  results = Results(survey=self.jobs.survey, data=self.results)
220
226
  else:
221
227
  # print("Running with progress bar")
222
- from rich.live import Live
223
- from rich.console import Console
224
228
 
225
229
  def generate_table():
226
230
  return self.status_table(self.results, self.elapsed_time)
@@ -1,5 +1,8 @@
1
1
  from edsl.jobs.tasks.task_status_enum import TaskStatus
2
+ from matplotlib import pyplot as plt
2
3
  from typing import List, Optional
4
+
5
+ import matplotlib.pyplot as plt
3
6
  from io import BytesIO
4
7
  import base64
5
8
 
@@ -72,8 +75,6 @@ class TaskHistory:
72
75
 
73
76
  def plot_completion_times(self):
74
77
  """Plot the completion times for each task."""
75
- import matplotlib.pyplot as plt
76
-
77
78
  updates = self.get_updates()
78
79
 
79
80
  elapsed = [update.max_time - update.min_time for update in updates]
@@ -125,8 +126,6 @@ class TaskHistory:
125
126
  rows = int(len(TaskStatus) ** 0.5) + 1
126
127
  cols = (len(TaskStatus) + rows - 1) // rows # Ensure all plots fit
127
128
 
128
- import matplotlib.pyplot as plt
129
-
130
129
  fig, axes = plt.subplots(rows, cols, figsize=(15, 10))
131
130
  axes = axes.flatten() # Flatten in case of a single row/column
132
131
 
@@ -7,18 +7,26 @@ import asyncio
7
7
  import json
8
8
  import time
9
9
  import os
10
+
10
11
  from typing import Coroutine, Any, Callable, Type, List, get_type_hints
11
- from abc import ABC, abstractmethod
12
+
13
+ from abc import ABC, abstractmethod, ABCMeta
14
+
15
+ from rich.table import Table
12
16
 
13
17
  from edsl.config import CONFIG
14
18
 
19
+ from edsl.utilities.utilities import clean_json
15
20
  from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
16
21
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
17
-
18
22
  from edsl.language_models.repair import repair
23
+ from edsl.exceptions.language_models import LanguageModelAttributeTypeError
19
24
  from edsl.enums import InferenceServiceType
20
25
  from edsl.Base import RichPrintingMixin, PersistenceMixin
26
+ from edsl.data.Cache import Cache
21
27
  from edsl.enums import service_to_api_keyname
28
+
29
+
22
30
  from edsl.exceptions import MissingAPIKeyError
23
31
  from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
24
32
 
@@ -283,7 +291,7 @@ class LanguageModel(
283
291
  self,
284
292
  user_prompt: str,
285
293
  system_prompt: str,
286
- cache: "Cache",
294
+ cache,
287
295
  iteration: int = 0,
288
296
  encoded_image=None,
289
297
  ) -> tuple[dict, bool, str]:
@@ -482,8 +490,6 @@ class LanguageModel(
482
490
 
483
491
  def rich_print(self):
484
492
  """Display an object as a table."""
485
- from rich.table import Table
486
-
487
493
  table = Table(title="Language Model")
488
494
  table.add_column("Attribute", style="bold")
489
495
  table.add_column("Value")
@@ -5,7 +5,7 @@ from edsl import Model
5
5
  from edsl.language_models import LanguageModel
6
6
  from edsl.Base import Base
7
7
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
8
- from edsl.utilities.utilities import is_valid_variable_name
8
+ from edsl.utilities import is_valid_variable_name
9
9
  from edsl.utilities.utilities import dict_hash
10
10
 
11
11
 
@@ -56,11 +56,11 @@ class ModelList(Base, UserList):
56
56
  return {"models": [model._to_dict() for model in self]}
57
57
 
58
58
  @classmethod
59
- def from_names(self, *args, **kwargs):
59
+ def from_names(self, *args):
60
60
  """A a model list from a list of names"""
61
61
  if len(args) == 1 and isinstance(args[0], list):
62
62
  args = args[0]
63
- return ModelList([Model(model_name, **kwargs) for model_name in args])
63
+ return ModelList([Model(model_name) for model_name in args])
64
64
 
65
65
  @add_edsl_version
66
66
  def to_dict(self):
@@ -1,13 +1,18 @@
1
1
  import json
2
2
  import asyncio
3
3
  import warnings
4
+ from rich import print
5
+ from rich.console import Console
6
+ from rich.syntax import Syntax
7
+
8
+ from edsl.utilities.utilities import clean_json
9
+
10
+ from edsl.utilities.repair_functions import extract_json_from_string
4
11
 
5
12
 
6
13
  async def async_repair(
7
14
  bad_json, error_message="", user_prompt=None, system_prompt=None, cache=None
8
15
  ):
9
- from edsl.utilities.utilities import clean_json
10
-
11
16
  s = clean_json(bad_json)
12
17
 
13
18
  try:
@@ -22,8 +27,6 @@ async def async_repair(
22
27
  return valid_dict, success
23
28
 
24
29
  try:
25
- from edsl.utilities.repair_functions import extract_json_from_string
26
-
27
30
  valid_dict = extract_json_from_string(s)
28
31
  success = True
29
32
  except ValueError:
@@ -95,10 +98,6 @@ async def async_repair(
95
98
  except json.JSONDecodeError:
96
99
  valid_dict = {}
97
100
  success = False
98
- from rich import print
99
- from rich.console import Console
100
- from rich.syntax import Syntax
101
-
102
101
  console = Console()
103
102
  error_message = (
104
103
  f"All repairs. failed. LLM Model given [red]{str(bad_json)}[/red]"
@@ -1,9 +1,10 @@
1
1
  """A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop."""
2
2
 
3
3
  import json
4
+ import nbformat
5
+ from nbconvert import HTMLExporter
4
6
  from typing import Dict, List, Optional
5
-
6
-
7
+ from rich.table import Table
7
8
  from edsl.Base import Base
8
9
  from edsl.utilities.decorators import (
9
10
  add_edsl_version,
@@ -33,8 +34,6 @@ class Notebook(Base):
33
34
  If no path is provided, assume this code is run in a notebook and try to load the current notebook from file.
34
35
  :param name: A name for the Notebook.
35
36
  """
36
- import nbformat
37
-
38
37
  # Load current notebook path as fallback (VS Code only)
39
38
  path = path or globals().get("__vsc_ipynb_file__")
40
39
  if data is not None:
@@ -57,37 +56,6 @@ class Notebook(Base):
57
56
 
58
57
  self.name = name or self.default_name
59
58
 
60
- @classmethod
61
- def from_script(cls, path: str, name: Optional[str] = None) -> "Notebook":
62
- # Read the script file
63
- with open(path, "r") as script_file:
64
- script_content = script_file.read()
65
-
66
- # Create a new Jupyter notebook
67
- nb = nbformat.v4.new_notebook()
68
-
69
- # Add the script content to the first cell
70
- first_cell = nbformat.v4.new_code_cell(script_content)
71
- nb.cells.append(first_cell)
72
-
73
- # Create a Notebook instance with the notebook data
74
- notebook_instance = cls(nb)
75
-
76
- return notebook_instance
77
-
78
- @classmethod
79
- def from_current_script(cls) -> "Notebook":
80
- import inspect
81
- import os
82
-
83
- # Get the path to the current file
84
- current_frame = inspect.currentframe()
85
- caller_frame = inspect.getouterframes(current_frame, 2)
86
- current_file_path = os.path.abspath(caller_frame[1].filename)
87
-
88
- # Use from_script to create the notebook
89
- return cls.from_script(current_file_path)
90
-
91
59
  def __eq__(self, other):
92
60
  """
93
61
  Check if two Notebooks are equal.
@@ -135,9 +103,6 @@ class Notebook(Base):
135
103
  """
136
104
  Return HTML representation of Notebook.
137
105
  """
138
- from nbconvert import HTMLExporter
139
- import nbformat
140
-
141
106
  notebook = nbformat.from_dict(self.data)
142
107
  html_exporter = HTMLExporter(template_name="basic")
143
108
  (body, _) = html_exporter.from_notebook_node(notebook)
@@ -178,8 +143,6 @@ class Notebook(Base):
178
143
  """
179
144
  Display a Notebook as a rich table.
180
145
  """
181
- from rich.table import Table
182
-
183
146
  table_data, column_names = self._table()
184
147
  table = Table(title=f"{self.__class__.__name__} Attributes")
185
148
  for column in column_names:
edsl/prompts/Prompt.py CHANGED
@@ -1,16 +1,12 @@
1
+ """Class for creating prompts to be used in a survey."""
2
+
1
3
  from __future__ import annotations
2
4
  from typing import Optional
3
5
  from abc import ABC
4
6
  from typing import Any, List
5
7
 
6
8
  from rich.table import Table
7
- from jinja2 import Template, Environment, meta, TemplateSyntaxError, Undefined
8
-
9
-
10
- class PreserveUndefined(Undefined):
11
- def __str__(self):
12
- return "{{ " + self._undefined_name + " }}"
13
-
9
+ from jinja2 import Template, Environment, meta, TemplateSyntaxError
14
10
 
15
11
  from edsl.exceptions.prompts import TemplateRenderError
16
12
  from edsl.prompts.prompt_config import (
@@ -39,10 +35,6 @@ class PromptBase(
39
35
 
40
36
  return data_to_html(self.to_dict())
41
37
 
42
- def __len__(self):
43
- """Return the length of the prompt text."""
44
- return len(self.text)
45
-
46
38
  @classmethod
47
39
  def prompt_attributes(cls) -> List[str]:
48
40
  """Return the prompt class attributes."""
@@ -83,10 +75,10 @@ class PromptBase(
83
75
  >>> p = Prompt("Hello, {{person}}")
84
76
  >>> p2 = Prompt("How are you?")
85
77
  >>> p + p2
86
- Prompt(text=\"""Hello, {{person}}How are you?\""")
78
+ Prompt(text='Hello, {{person}}How are you?')
87
79
 
88
80
  >>> p + "How are you?"
89
- Prompt(text=\"""Hello, {{person}}How are you?\""")
81
+ Prompt(text='Hello, {{person}}How are you?')
90
82
  """
91
83
  if isinstance(other_prompt, str):
92
84
  return self.__class__(self.text + other_prompt)
@@ -122,7 +114,7 @@ class PromptBase(
122
114
  Example:
123
115
  >>> p = Prompt("Hello, {{person}}")
124
116
  >>> p
125
- Prompt(text=\"""Hello, {{person}}\""")
117
+ Prompt(text='Hello, {{person}}')
126
118
  """
127
119
  return f'Prompt(text="""{self.text}""")'
128
120
 
@@ -145,7 +137,7 @@ class PromptBase(
145
137
  :param template: The template to find the variables in.
146
138
 
147
139
  """
148
- env = Environment(undefined=PreserveUndefined)
140
+ env = Environment()
149
141
  ast = env.parse(template)
150
142
  return list(meta.find_undeclared_variables(ast))
151
143
 
@@ -194,16 +186,13 @@ class PromptBase(
194
186
 
195
187
  >>> p = Prompt("Hello, {{person}}")
196
188
  >>> p.render({"person": "John"})
197
- Prompt(text=\"""Hello, John\""")
189
+ 'Hello, John'
198
190
 
199
191
  >>> p.render({"person": "Mr. {{last_name}}", "last_name": "Horton"})
200
- Prompt(text=\"""Hello, Mr. Horton\""")
192
+ 'Hello, Mr. Horton'
201
193
 
202
194
  >>> p.render({"person": "Mr. {{last_name}}", "last_name": "Ho{{letter}}ton"}, max_nesting = 1)
203
- Prompt(text=\"""Hello, Mr. Ho{{ letter }}ton\""")
204
-
205
- >>> p.render({"person": "Mr. {{last_name}}"})
206
- Prompt(text=\"""Hello, Mr. {{ last_name }}\""")
195
+ 'Hello, Mr. Horton'
207
196
  """
208
197
  new_text = self._render(
209
198
  self.text, primary_replacement, **additional_replacements
@@ -227,13 +216,12 @@ class PromptBase(
227
216
  >>> codebook = {"age": "Age"}
228
217
  >>> p = Prompt("You are an agent named {{ name }}. {{ codebook['age']}}: {{ age }}")
229
218
  >>> p.render({"name": "John", "age": 44}, codebook=codebook)
230
- Prompt(text=\"""You are an agent named John. Age: 44\""")
219
+ 'You are an agent named John. Age: 44'
231
220
  """
232
- env = Environment(undefined=PreserveUndefined)
233
221
  try:
234
222
  previous_text = None
235
223
  for _ in range(MAX_NESTING):
236
- rendered_text = env.from_string(text).render(
224
+ rendered_text = Template(text).render(
237
225
  primary_replacement, **additional_replacements
238
226
  )
239
227
  if rendered_text == previous_text:
@@ -270,7 +258,7 @@ class PromptBase(
270
258
  >>> p = Prompt("Hello, {{person}}")
271
259
  >>> p2 = Prompt.from_dict(p.to_dict())
272
260
  >>> p2
273
- Prompt(text=\"""Hello, {{person}}\""")
261
+ Prompt(text='Hello, {{person}}')
274
262
 
275
263
  """
276
264
  class_name = data["class_name"]
@@ -302,12 +290,6 @@ class Prompt(PromptBase):
302
290
  component_type = ComponentTypes.GENERIC
303
291
 
304
292
 
305
- if __name__ == "__main__":
306
- print("Running doctests...")
307
- import doctest
308
-
309
- doctest.testmod()
310
-
311
293
  from edsl.prompts.library.question_multiple_choice import *
312
294
  from edsl.prompts.library.agent_instructions import *
313
295
  from edsl.prompts.library.agent_persona import *
@@ -320,3 +302,9 @@ from edsl.prompts.library.question_numerical import *
320
302
  from edsl.prompts.library.question_rank import *
321
303
  from edsl.prompts.library.question_extract import *
322
304
  from edsl.prompts.library.question_list import *
305
+
306
+
307
+ if __name__ == "__main__":
308
+ import doctest
309
+
310
+ doctest.testmod()
@@ -1,8 +1,8 @@
1
1
  """This module contains the Question class, which is the base class for all questions in EDSL."""
2
2
 
3
3
  from __future__ import annotations
4
- import time
5
4
  from abc import ABC, abstractmethod
5
+ from rich.table import Table
6
6
  from typing import Any, Type, Optional, List, Callable
7
7
  import copy
8
8
 
@@ -12,7 +12,7 @@ from edsl.exceptions import (
12
12
  )
13
13
  from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
14
14
 
15
-
15
+ from edsl.prompts.registry import get_classes as prompt_lookup
16
16
  from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
17
17
  from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
18
18
  from edsl.Base import PersistenceMixin, RichPrintingMixin
@@ -124,8 +124,6 @@ class QuestionBase(
124
124
  :param model: The language model to use. If None, assumes does not matter.
125
125
 
126
126
  """
127
- from edsl.prompts.registry import get_classes as prompt_lookup
128
-
129
127
  applicable_prompts = prompt_lookup(
130
128
  component_type="question_instructions",
131
129
  question_type=cls.question_type,
@@ -175,16 +173,15 @@ class QuestionBase(
175
173
  def add_model_instructions(
176
174
  self, *, instructions: str, model: Optional[str] = None
177
175
  ) -> None:
178
- """Add model-specific instructions for the question that override the default instructions.
176
+ """Add model-specific instructions for the question.
179
177
 
180
178
  :param instructions: The instructions to add. This is typically a jinja2 template.
181
179
  :param model: The language model for this instruction.
182
180
 
183
181
  >>> from edsl.questions import QuestionFreeText
184
182
  >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
185
- >>> q.add_model_instructions(instructions = "{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
186
- >>> q.get_instructions(model = "gpt3")
187
- Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
183
+ >>> q.add_model_instructions(instructions = "Answer in valid JSON like so {'answer': 'comment: <>}", model = "gpt3")
184
+
188
185
  """
189
186
  from edsl import Model
190
187
 
@@ -204,13 +201,6 @@ class QuestionBase(
204
201
  """Get the mathcing question-answering instructions for the question.
205
202
 
206
203
  :param model: The language model to use.
207
-
208
- >>> from edsl import QuestionFreeText
209
- >>> QuestionFreeText.example().get_instructions()
210
- Prompt(text=\"""You are being asked the following question: {{question_text}}
211
- Return a valid JSON formatted like this:
212
- {"answer": "<put free text answer here>"}
213
- \""")
214
204
  """
215
205
  from edsl.prompts.Prompt import Prompt
216
206
 
@@ -303,16 +293,7 @@ class QuestionBase(
303
293
  print_json(json.dumps(self.to_dict()))
304
294
 
305
295
  def __call__(self, just_answer=True, model=None, agent=None, **kwargs):
306
- """Call the question.
307
-
308
- >>> from edsl.language_models import LanguageModel
309
- >>> m = LanguageModel.example(canned_response = "Yo, what's up?", test_model = True)
310
- >>> from edsl import QuestionFreeText
311
- >>> q = QuestionFreeText(question_name = "color", question_text = "What is your favorite color?")
312
- >>> q(model = m)
313
- "Yo, what's up?"
314
-
315
- """
296
+ """Call the question."""
316
297
  survey = self.to_survey()
317
298
  results = survey(model=model, agent=agent, **kwargs)
318
299
  if just_answer:
@@ -323,6 +304,7 @@ class QuestionBase(
323
304
  async def run_async(self, just_answer=True, model=None, agent=None, **kwargs):
324
305
  """Call the question."""
325
306
  survey = self.to_survey()
307
+ ## asyncio.run(survey.async_call());
326
308
  results = await survey.run_async(model=model, agent=agent, **kwargs)
327
309
  if just_answer:
328
310
  return results.select(f"answer.{self.question_name}").first()
@@ -401,34 +383,29 @@ class QuestionBase(
401
383
  s = Survey([self, other])
402
384
  return s
403
385
 
404
- def to_survey(self) -> "Survey":
386
+ def to_survey(self):
405
387
  """Turn a single question into a survey."""
406
388
  from edsl.surveys.Survey import Survey
407
389
 
408
390
  s = Survey([self])
409
391
  return s
410
392
 
411
- def run(self, *args, **kwargs) -> "Results":
393
+ def run(self, *args, **kwargs):
412
394
  """Turn a single question into a survey and run it."""
413
395
  from edsl.surveys.Survey import Survey
414
396
 
415
397
  s = self.to_survey()
416
398
  return s.run(*args, **kwargs)
417
399
 
418
- def by(self, *args) -> "Jobs":
419
- """Turn a single question into a survey and then a Job."""
400
+ def by(self, *args):
401
+ """Turn a single question into a survey and run it."""
420
402
  from edsl.surveys.Survey import Survey
421
403
 
422
404
  s = Survey([self])
423
405
  return s.by(*args)
424
406
 
425
- def human_readable(self) -> str:
426
- """Print the question in a human readable format.
427
-
428
- >>> from edsl.questions import QuestionFreeText
429
- >>> QuestionFreeText.example().human_readable()
430
- 'Question Type: free_text\\nQuestion: How are you?'
431
- """
407
+ def human_readable(self):
408
+ """Print the question in a human readable format."""
432
409
  lines = []
433
410
  lines.append(f"Question Type: {self.question_type}")
434
411
  lines.append(f"Question: {self.question_text}")
@@ -498,8 +475,6 @@ class QuestionBase(
498
475
 
499
476
  def rich_print(self):
500
477
  """Print the question in a rich format."""
501
- from rich.table import Table
502
-
503
478
  table = Table(show_header=True, header_style="bold magenta")
504
479
  table.add_column("Question Name", style="dim")
505
480
  table.add_column("Question Type")
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
2
  import random
3
+ import textwrap
3
4
  from typing import Any, Optional, Union
4
5
  from edsl.questions.QuestionBase import QuestionBase
5
6
  from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
7
+ from edsl.scenarios import Scenario
8
+ from edsl.utilities import random_string
6
9
 
7
10
 
8
11
  class QuestionBudget(QuestionBase):
@@ -43,7 +46,7 @@ class QuestionBudget(QuestionBase):
43
46
  return answer
44
47
 
45
48
  def _translate_answer_code_to_answer(
46
- self, answer_codes: dict[str, int], scenario: "Scenario" = None
49
+ self, answer_codes: dict[str, int], scenario: Scenario = None
47
50
  ):
48
51
  """
49
52
  Translate the answer codes to the actual answers.
@@ -60,8 +63,6 @@ class QuestionBudget(QuestionBase):
60
63
 
61
64
  def _simulate_answer(self, human_readable=True):
62
65
  """Simulate a valid answer for debugging purposes (what the validator expects)."""
63
- from edsl.utilities.utilities import random_string
64
-
65
66
  if human_readable:
66
67
  keys = self.question_options
67
68
  else:
@@ -162,8 +163,8 @@ def main():
162
163
 
163
164
 
164
165
  if __name__ == "__main__":
165
- # q = QuestionBudget.example()
166
- # results = q.run()
166
+ q = QuestionBudget.example()
167
+ results = q.run()
167
168
 
168
169
  import doctest
169
170
 
@@ -9,6 +9,8 @@ from edsl.questions.descriptors import (
9
9
  IntegerDescriptor,
10
10
  QuestionOptionsDescriptor,
11
11
  )
12
+ from edsl.scenarios import Scenario
13
+ from edsl.utilities import random_string
12
14
 
13
15
 
14
16
  class QuestionCheckBox(QuestionBase):
@@ -53,17 +55,13 @@ class QuestionCheckBox(QuestionBase):
53
55
  self._validate_answer_checkbox(answer)
54
56
  return answer
55
57
 
56
- def _translate_answer_code_to_answer(
57
- self, answer_codes, scenario: "Scenario" = None
58
- ):
58
+ def _translate_answer_code_to_answer(self, answer_codes, scenario: Scenario = None):
59
59
  """
60
60
  Translate the answer code to the actual answer.
61
61
 
62
62
  For example, for question options ["a", "b", "c"],the answer codes are 0, 1, and 2.
63
63
  The LLM will respond with [0,1] and this code will translate it to ["a","b"].
64
64
  """
65
- from edsl.scenarios.Scenario import Scenario
66
-
67
65
  scenario = scenario or Scenario()
68
66
  translated_options = [
69
67
  Template(option).render(scenario) for option in self.question_options
@@ -75,8 +73,6 @@ class QuestionCheckBox(QuestionBase):
75
73
 
76
74
  def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
77
75
  """Simulate a valid answer for debugging purposes."""
78
- from edsl.utilities.utilities import random_string
79
-
80
76
  min_selections = self.min_selections or 1
81
77
  max_selections = self.max_selections or len(self.question_options)
82
78
  num_selections = random.randint(min_selections, max_selections)