edsl 0.1.29.dev6__py3-none-any.whl → 0.1.30__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 (65) hide show
  1. edsl/Base.py +6 -3
  2. edsl/__init__.py +23 -23
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +43 -40
  5. edsl/agents/AgentList.py +23 -22
  6. edsl/agents/Invigilator.py +19 -2
  7. edsl/agents/descriptors.py +2 -1
  8. edsl/base/Base.py +289 -0
  9. edsl/config.py +2 -1
  10. edsl/conversation/car_buying.py +1 -1
  11. edsl/coop/utils.py +28 -1
  12. edsl/data/Cache.py +41 -18
  13. edsl/data/CacheEntry.py +6 -7
  14. edsl/data/SQLiteDict.py +11 -3
  15. edsl/data_transfer_models.py +4 -0
  16. edsl/jobs/Answers.py +15 -1
  17. edsl/jobs/Jobs.py +86 -33
  18. edsl/jobs/buckets/ModelBuckets.py +14 -2
  19. edsl/jobs/buckets/TokenBucket.py +32 -5
  20. edsl/jobs/interviews/Interview.py +99 -79
  21. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +18 -24
  22. edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
  23. edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
  24. edsl/jobs/tasks/TaskHistory.py +4 -3
  25. edsl/language_models/LanguageModel.py +17 -17
  26. edsl/language_models/ModelList.py +1 -1
  27. edsl/language_models/repair.py +8 -7
  28. edsl/notebooks/Notebook.py +16 -10
  29. edsl/questions/QuestionBase.py +6 -2
  30. edsl/questions/QuestionBudget.py +5 -6
  31. edsl/questions/QuestionCheckBox.py +7 -3
  32. edsl/questions/QuestionExtract.py +5 -3
  33. edsl/questions/QuestionFreeText.py +7 -5
  34. edsl/questions/QuestionFunctional.py +34 -5
  35. edsl/questions/QuestionList.py +3 -4
  36. edsl/questions/QuestionMultipleChoice.py +68 -12
  37. edsl/questions/QuestionNumerical.py +4 -3
  38. edsl/questions/QuestionRank.py +5 -3
  39. edsl/questions/__init__.py +4 -3
  40. edsl/questions/descriptors.py +46 -4
  41. edsl/results/DatasetExportMixin.py +570 -0
  42. edsl/results/Result.py +66 -70
  43. edsl/results/Results.py +160 -68
  44. edsl/results/ResultsDBMixin.py +7 -3
  45. edsl/results/ResultsExportMixin.py +22 -537
  46. edsl/results/ResultsGGMixin.py +3 -3
  47. edsl/results/ResultsToolsMixin.py +1 -4
  48. edsl/scenarios/FileStore.py +299 -0
  49. edsl/scenarios/Scenario.py +16 -24
  50. edsl/scenarios/ScenarioList.py +25 -14
  51. edsl/scenarios/ScenarioListExportMixin.py +32 -0
  52. edsl/scenarios/ScenarioListPdfMixin.py +2 -1
  53. edsl/scenarios/__init__.py +1 -0
  54. edsl/study/Study.py +5 -7
  55. edsl/surveys/MemoryPlan.py +11 -4
  56. edsl/surveys/Survey.py +52 -15
  57. edsl/surveys/SurveyExportMixin.py +4 -2
  58. edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
  59. edsl/utilities/__init__.py +21 -21
  60. edsl/utilities/interface.py +66 -45
  61. edsl/utilities/utilities.py +11 -13
  62. {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/METADATA +1 -1
  63. {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/RECORD +65 -61
  64. {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
  65. {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
@@ -5,17 +5,19 @@ import asyncio
5
5
  import time
6
6
  import traceback
7
7
  from typing import Generator, Union
8
+
8
9
  from edsl import CONFIG
9
10
  from edsl.exceptions import InterviewTimeoutError
10
- from edsl.data_transfer_models import AgentResponseDict
11
- from edsl.questions.QuestionBase import QuestionBase
11
+
12
+ # from edsl.questions.QuestionBase import QuestionBase
12
13
  from edsl.surveys.base import EndOfSurvey
13
14
  from edsl.jobs.buckets.ModelBuckets import ModelBuckets
14
15
  from edsl.jobs.interviews.interview_exception_tracking import InterviewExceptionEntry
15
16
  from edsl.jobs.interviews.retry_management import retry_strategy
16
17
  from edsl.jobs.tasks.task_status_enum import TaskStatus
17
18
  from edsl.jobs.tasks.QuestionTaskCreator import QuestionTaskCreator
18
- from edsl.agents.InvigilatorBase import InvigilatorBase
19
+
20
+ # from edsl.agents.InvigilatorBase import InvigilatorBase
19
21
 
20
22
  TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
21
23
 
@@ -23,7 +25,7 @@ TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
23
25
  class InterviewTaskBuildingMixin:
24
26
  def _build_invigilators(
25
27
  self, debug: bool
26
- ) -> Generator[InvigilatorBase, None, None]:
28
+ ) -> Generator["InvigilatorBase", None, None]:
27
29
  """Create an invigilator for each question.
28
30
 
29
31
  :param debug: whether to use debug mode, in which case `InvigilatorDebug` is used.
@@ -33,7 +35,7 @@ class InterviewTaskBuildingMixin:
33
35
  for question in self.survey.questions:
34
36
  yield self._get_invigilator(question=question, debug=debug)
35
37
 
36
- def _get_invigilator(self, question: QuestionBase, debug: bool) -> "Invigilator":
38
+ def _get_invigilator(self, question: "QuestionBase", debug: bool) -> "Invigilator":
37
39
  """Return an invigilator for the given question.
38
40
 
39
41
  :param question: the question to be answered
@@ -82,7 +84,7 @@ class InterviewTaskBuildingMixin:
82
84
  return tuple(tasks) # , invigilators
83
85
 
84
86
  def _get_tasks_that_must_be_completed_before(
85
- self, *, tasks: list[asyncio.Task], question: QuestionBase
87
+ self, *, tasks: list[asyncio.Task], question: "QuestionBase"
86
88
  ) -> Generator[asyncio.Task, None, None]:
87
89
  """Return the tasks that must be completed before the given question can be answered.
88
90
 
@@ -98,7 +100,7 @@ class InterviewTaskBuildingMixin:
98
100
  def _create_question_task(
99
101
  self,
100
102
  *,
101
- question: QuestionBase,
103
+ question: "QuestionBase",
102
104
  tasks_that_must_be_completed_before: list[asyncio.Task],
103
105
  model_buckets: ModelBuckets,
104
106
  debug: bool,
@@ -150,15 +152,17 @@ class InterviewTaskBuildingMixin:
150
152
  async def _answer_question_and_record_task(
151
153
  self,
152
154
  *,
153
- question: QuestionBase,
155
+ question: "QuestionBase",
154
156
  debug: bool,
155
157
  task=None,
156
- ) -> AgentResponseDict:
158
+ ) -> "AgentResponseDict":
157
159
  """Answer a question and records the task.
158
160
 
159
161
  This in turn calls the the passed-in agent's async_answer_question method, which returns a response dictionary.
160
162
  Note that is updates answers dictionary with the response.
161
163
  """
164
+ from edsl.data_transfer_models import AgentResponseDict
165
+
162
166
  try:
163
167
  invigilator = self._get_invigilator(question, debug=debug)
164
168
 
@@ -171,24 +175,14 @@ class InterviewTaskBuildingMixin:
171
175
 
172
176
  self._add_answer(response=response, question=question)
173
177
 
174
- # With the answer to the question, we can now cancel any skipped questions
175
178
  self._cancel_skipped_questions(question)
176
179
  return AgentResponseDict(**response)
177
180
  except Exception as e:
178
181
  raise e
179
- # import traceback
180
- # print("Exception caught:")
181
- # traceback.print_exc()
182
-
183
- # # Extract and print the traceback info
184
- # tb = e.__traceback__
185
- # while tb is not None:
186
- # print(f"File {tb.tb_frame.f_code.co_filename}, line {tb.tb_lineno}, in {tb.tb_frame.f_code.co_name}")
187
- # tb = tb.tb_next
188
- # breakpoint()
189
- # raise e
190
-
191
- def _add_answer(self, response: AgentResponseDict, question: QuestionBase) -> None:
182
+
183
+ def _add_answer(
184
+ self, response: "AgentResponseDict", question: "QuestionBase"
185
+ ) -> None:
192
186
  """Add the answer to the answers dictionary.
193
187
 
194
188
  :param response: the response to the question.
@@ -196,7 +190,7 @@ class InterviewTaskBuildingMixin:
196
190
  """
197
191
  self.answers.add_answer(response=response, question=question)
198
192
 
199
- def _skip_this_question(self, current_question: QuestionBase) -> bool:
193
+ def _skip_this_question(self, current_question: "QuestionBase") -> bool:
200
194
  """Determine if the current question should be skipped.
201
195
 
202
196
  :param current_question: the question to be answered.
@@ -1,29 +1,17 @@
1
1
  from __future__ import annotations
2
2
  import time
3
3
  import asyncio
4
- import textwrap
4
+ import time
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
-
12
9
  from edsl import shared_globals
13
- from edsl.results import Results, Result
14
-
15
10
  from edsl.jobs.interviews.Interview import Interview
16
- from edsl.utilities.decorators import jupyter_nb_handler
17
-
18
- # from edsl.jobs.Jobs import Jobs
19
11
  from edsl.jobs.runners.JobsRunnerStatusMixin import JobsRunnerStatusMixin
20
- from edsl.language_models import LanguageModel
21
- from edsl.data.Cache import Cache
22
-
23
12
  from edsl.jobs.tasks.TaskHistory import TaskHistory
24
13
  from edsl.jobs.buckets.BucketCollection import BucketCollection
25
-
26
- import time
14
+ from edsl.utilities.decorators import jupyter_nb_handler
27
15
 
28
16
 
29
17
  class JobsRunnerAsyncio(JobsRunnerStatusMixin):
@@ -42,13 +30,13 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
42
30
 
43
31
  async def run_async_generator(
44
32
  self,
45
- cache: Cache,
33
+ cache: "Cache",
46
34
  n: int = 1,
47
35
  debug: bool = False,
48
36
  stop_on_exception: bool = False,
49
37
  sidecar_model: "LanguageModel" = None,
50
38
  total_interviews: Optional[List["Interview"]] = None,
51
- ) -> AsyncGenerator[Result, None]:
39
+ ) -> AsyncGenerator["Result", None]:
52
40
  """Creates the tasks, runs them asynchronously, and returns the results as a Results object.
53
41
 
54
42
  Completed tasks are yielded as they are completed.
@@ -100,6 +88,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
100
88
  self.total_interviews.append(interview)
101
89
 
102
90
  async def run_async(self, cache=None) -> Results:
91
+ from edsl.results.Results import Results
92
+
103
93
  if cache is None:
104
94
  self.cache = Cache()
105
95
  else:
@@ -110,6 +100,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
110
100
  return Results(survey=self.jobs.survey, data=data)
111
101
 
112
102
  def simple_run(self):
103
+ from edsl.results.Results import Results
104
+
113
105
  data = asyncio.run(self.run_async())
114
106
  return Results(survey=self.jobs.survey, data=data)
115
107
 
@@ -169,6 +161,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
169
161
  question_name + "_raw_model_response"
170
162
  ] = result["raw_model_response"]
171
163
 
164
+ from edsl.results.Result import Result
165
+
172
166
  result = Result(
173
167
  agent=interview.agent,
174
168
  scenario=interview.scenario,
@@ -197,6 +191,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
197
191
  print_exceptions: bool = True,
198
192
  ) -> "Coroutine":
199
193
  """Runs a collection of interviews, handling both async and sync contexts."""
194
+ from rich.console import Console
195
+
200
196
  console = Console()
201
197
  self.results = []
202
198
  self.start_time = time.monotonic()
@@ -204,6 +200,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
204
200
  self.cache = cache
205
201
  self.sidecar_model = sidecar_model
206
202
 
203
+ from edsl.results.Results import Results
204
+
207
205
  if not progress_bar:
208
206
  # print("Running without progress bar")
209
207
  with cache as c:
@@ -225,6 +223,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
225
223
  results = Results(survey=self.jobs.survey, data=self.results)
226
224
  else:
227
225
  # print("Running with progress bar")
226
+ from rich.live import Live
227
+ from rich.console import Console
228
228
 
229
229
  def generate_table():
230
230
  return self.status_table(self.results, self.elapsed_time)
@@ -144,12 +144,16 @@ class QuestionTaskCreator(UserList):
144
144
  self.task_status = TaskStatus.FAILED
145
145
  raise e
146
146
 
147
- if "cached_response" in results:
148
- if results["cached_response"]:
149
- # Gives back the tokens b/c the API was not called.
150
- self.tokens_bucket.add_tokens(requested_tokens)
151
- self.requests_bucket.add_tokens(1)
152
- self.from_cache = True
147
+ if results.get("cache_used", False):
148
+ self.tokens_bucket.add_tokens(requested_tokens)
149
+ self.requests_bucket.add_tokens(1)
150
+ self.from_cache = True
151
+ # Turbo mode means that we don't wait for tokens or requests.
152
+ self.tokens_bucket.turbo_mode_on()
153
+ self.requests_bucket.turbo_mode_on()
154
+ else:
155
+ self.tokens_bucket.turbo_mode_off()
156
+ self.requests_bucket.turbo_mode_off()
153
157
 
154
158
  _ = results.pop("cached_response", None)
155
159
 
@@ -1,8 +1,5 @@
1
1
  from edsl.jobs.tasks.task_status_enum import TaskStatus
2
- from matplotlib import pyplot as plt
3
2
  from typing import List, Optional
4
-
5
- import matplotlib.pyplot as plt
6
3
  from io import BytesIO
7
4
  import base64
8
5
 
@@ -75,6 +72,8 @@ class TaskHistory:
75
72
 
76
73
  def plot_completion_times(self):
77
74
  """Plot the completion times for each task."""
75
+ import matplotlib.pyplot as plt
76
+
78
77
  updates = self.get_updates()
79
78
 
80
79
  elapsed = [update.max_time - update.min_time for update in updates]
@@ -126,6 +125,8 @@ class TaskHistory:
126
125
  rows = int(len(TaskStatus) ** 0.5) + 1
127
126
  cols = (len(TaskStatus) + rows - 1) // rows # Ensure all plots fit
128
127
 
128
+ import matplotlib.pyplot as plt
129
+
129
130
  fig, axes = plt.subplots(rows, cols, figsize=(15, 10))
130
131
  axes = axes.flatten() # Flatten in case of a single row/column
131
132
 
@@ -7,26 +7,18 @@ import asyncio
7
7
  import json
8
8
  import time
9
9
  import os
10
-
11
10
  from typing import Coroutine, Any, Callable, Type, List, get_type_hints
12
-
13
- from abc import ABC, abstractmethod, ABCMeta
14
-
15
- from rich.table import Table
11
+ from abc import ABC, abstractmethod
16
12
 
17
13
  from edsl.config import CONFIG
18
14
 
19
- from edsl.utilities.utilities import clean_json
20
15
  from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
21
16
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
17
+
22
18
  from edsl.language_models.repair import repair
23
- from edsl.exceptions.language_models import LanguageModelAttributeTypeError
24
19
  from edsl.enums import InferenceServiceType
25
20
  from edsl.Base import RichPrintingMixin, PersistenceMixin
26
- from edsl.data.Cache import Cache
27
21
  from edsl.enums import service_to_api_keyname
28
-
29
-
30
22
  from edsl.exceptions import MissingAPIKeyError
31
23
  from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
32
24
 
@@ -291,7 +283,7 @@ class LanguageModel(
291
283
  self,
292
284
  user_prompt: str,
293
285
  system_prompt: str,
294
- cache,
286
+ cache: "Cache",
295
287
  iteration: int = 0,
296
288
  encoded_image=None,
297
289
  ) -> tuple[dict, bool, str]:
@@ -331,12 +323,10 @@ class LanguageModel(
331
323
  image_hash = hashlib.md5(encoded_image.encode()).hexdigest()
332
324
  cache_call_params["user_prompt"] = f"{user_prompt} {image_hash}"
333
325
 
334
- cached_response = cache.fetch(**cache_call_params)
335
-
326
+ cached_response, cache_key = cache.fetch(**cache_call_params)
336
327
  if cached_response:
337
328
  response = json.loads(cached_response)
338
329
  cache_used = True
339
- cache_key = None
340
330
  else:
341
331
  remote_call = hasattr(self, "remote") and self.remote
342
332
  f = (
@@ -348,7 +338,7 @@ class LanguageModel(
348
338
  if encoded_image:
349
339
  params["encoded_image"] = encoded_image
350
340
  response = await f(**params)
351
- cache_key = cache.store(
341
+ new_cache_key = cache.store(
352
342
  user_prompt=user_prompt,
353
343
  model=str(self.model),
354
344
  parameters=self.parameters,
@@ -356,6 +346,7 @@ class LanguageModel(
356
346
  response=response,
357
347
  iteration=iteration,
358
348
  )
349
+ assert new_cache_key == cache_key
359
350
  cache_used = False
360
351
 
361
352
  return response, cache_used, cache_key
@@ -420,7 +411,7 @@ class LanguageModel(
420
411
 
421
412
  dict_response.update(
422
413
  {
423
- "cached_used": cache_used,
414
+ "cache_used": cache_used,
424
415
  "cache_key": cache_key,
425
416
  "usage": raw_response.get("usage", {}),
426
417
  "raw_model_response": raw_response,
@@ -490,6 +481,8 @@ class LanguageModel(
490
481
 
491
482
  def rich_print(self):
492
483
  """Display an object as a table."""
484
+ from rich.table import Table
485
+
493
486
  table = Table(title="Language Model")
494
487
  table.add_column("Attribute", style="bold")
495
488
  table.add_column("Value")
@@ -501,7 +494,12 @@ class LanguageModel(
501
494
  return table
502
495
 
503
496
  @classmethod
504
- def example(cls, test_model: bool = False, canned_response: str = "Hello world"):
497
+ def example(
498
+ cls,
499
+ test_model: bool = False,
500
+ canned_response: str = "Hello world",
501
+ throw_exception: bool = False,
502
+ ):
505
503
  """Return a default instance of the class.
506
504
 
507
505
  >>> from edsl.language_models import LanguageModel
@@ -526,6 +524,8 @@ class LanguageModel(
526
524
  ) -> dict[str, Any]:
527
525
  await asyncio.sleep(0.1)
528
526
  # return {"message": """{"answer": "Hello, world"}"""}
527
+ if throw_exception:
528
+ raise Exception("This is a test error")
529
529
  return {"message": f'{{"answer": "{canned_response}"}}'}
530
530
 
531
531
  def parse_response(self, raw_response: dict[str, Any]) -> str:
@@ -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 import is_valid_variable_name
8
+ from edsl.utilities.utilities import is_valid_variable_name
9
9
  from edsl.utilities.utilities import dict_hash
10
10
 
11
11
 
@@ -1,18 +1,13 @@
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
11
4
 
12
5
 
13
6
  async def async_repair(
14
7
  bad_json, error_message="", user_prompt=None, system_prompt=None, cache=None
15
8
  ):
9
+ from edsl.utilities.utilities import clean_json
10
+
16
11
  s = clean_json(bad_json)
17
12
 
18
13
  try:
@@ -27,6 +22,8 @@ async def async_repair(
27
22
  return valid_dict, success
28
23
 
29
24
  try:
25
+ from edsl.utilities.repair_functions import extract_json_from_string
26
+
30
27
  valid_dict = extract_json_from_string(s)
31
28
  success = True
32
29
  except ValueError:
@@ -98,6 +95,10 @@ async def async_repair(
98
95
  except json.JSONDecodeError:
99
96
  valid_dict = {}
100
97
  success = False
98
+ from rich import print
99
+ from rich.console import Console
100
+ from rich.syntax import Syntax
101
+
101
102
  console = Console()
102
103
  error_message = (
103
104
  f"All repairs. failed. LLM Model given [red]{str(bad_json)}[/red]"
@@ -1,15 +1,11 @@
1
1
  """A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop."""
2
2
 
3
+ from __future__ import annotations
3
4
  import json
4
- import nbformat
5
- from nbconvert import HTMLExporter
6
5
  from typing import Dict, List, Optional
7
- from rich.table import Table
6
+ from uuid import uuid4
8
7
  from edsl.Base import Base
9
- from edsl.utilities.decorators import (
10
- add_edsl_version,
11
- remove_edsl_version,
12
- )
8
+ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
13
9
 
14
10
 
15
11
  class Notebook(Base):
@@ -34,6 +30,8 @@ class Notebook(Base):
34
30
  If no path is provided, assume this code is run in a notebook and try to load the current notebook from file.
35
31
  :param name: A name for the Notebook.
36
32
  """
33
+ import nbformat
34
+
37
35
  # Load current notebook path as fallback (VS Code only)
38
36
  path = path or globals().get("__vsc_ipynb_file__")
39
37
  if data is not None:
@@ -134,6 +132,9 @@ class Notebook(Base):
134
132
  """
135
133
  Return HTML representation of Notebook.
136
134
  """
135
+ from nbconvert import HTMLExporter
136
+ import nbformat
137
+
137
138
  notebook = nbformat.from_dict(self.data)
138
139
  html_exporter = HTMLExporter(template_name="basic")
139
140
  (body, _) = html_exporter.from_notebook_node(notebook)
@@ -174,6 +175,8 @@ class Notebook(Base):
174
175
  """
175
176
  Display a Notebook as a rich table.
176
177
  """
178
+ from rich.table import Table
179
+
177
180
  table_data, column_names = self._table()
178
181
  table = Table(title=f"{self.__class__.__name__} Attributes")
179
182
  for column in column_names:
@@ -186,10 +189,13 @@ class Notebook(Base):
186
189
  return table
187
190
 
188
191
  @classmethod
189
- def example(cls) -> "Notebook":
192
+ def example(cls, randomize: bool = False) -> Notebook:
190
193
  """
191
- Return an example Notebook.
194
+ Returns an example Notebook instance.
195
+
196
+ :param randomize: If True, adds a random string one of the cells' output.
192
197
  """
198
+ addition = "" if not randomize else str(uuid4())
193
199
  cells = [
194
200
  {
195
201
  "cell_type": "markdown",
@@ -204,7 +210,7 @@ class Notebook(Base):
204
210
  {
205
211
  "name": "stdout",
206
212
  "output_type": "stream",
207
- "text": "Hello world!\n",
213
+ "text": f"Hello world!\n{addition}",
208
214
  }
209
215
  ],
210
216
  "source": 'print("Hello world!")',
@@ -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
4
5
  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
- from edsl.prompts.registry import get_classes as prompt_lookup
15
+
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,6 +124,8 @@ 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
+
127
129
  applicable_prompts = prompt_lookup(
128
130
  component_type="question_instructions",
129
131
  question_type=cls.question_type,
@@ -496,6 +498,8 @@ class QuestionBase(
496
498
 
497
499
  def rich_print(self):
498
500
  """Print the question in a rich format."""
501
+ from rich.table import Table
502
+
499
503
  table = Table(show_header=True, header_style="bold magenta")
500
504
  table.add_column("Question Name", style="dim")
501
505
  table.add_column("Question Type")
@@ -1,11 +1,8 @@
1
1
  from __future__ import annotations
2
2
  import random
3
- import textwrap
4
3
  from typing import Any, Optional, Union
5
4
  from edsl.questions.QuestionBase import QuestionBase
6
5
  from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
7
- from edsl.scenarios import Scenario
8
- from edsl.utilities import random_string
9
6
 
10
7
 
11
8
  class QuestionBudget(QuestionBase):
@@ -46,7 +43,7 @@ class QuestionBudget(QuestionBase):
46
43
  return answer
47
44
 
48
45
  def _translate_answer_code_to_answer(
49
- self, answer_codes: dict[str, int], scenario: Scenario = None
46
+ self, answer_codes: dict[str, int], scenario: "Scenario" = None
50
47
  ):
51
48
  """
52
49
  Translate the answer codes to the actual answers.
@@ -63,6 +60,8 @@ class QuestionBudget(QuestionBase):
63
60
 
64
61
  def _simulate_answer(self, human_readable=True):
65
62
  """Simulate a valid answer for debugging purposes (what the validator expects)."""
63
+ from edsl.utilities.utilities import random_string
64
+
66
65
  if human_readable:
67
66
  keys = self.question_options
68
67
  else:
@@ -163,8 +162,8 @@ def main():
163
162
 
164
163
 
165
164
  if __name__ == "__main__":
166
- q = QuestionBudget.example()
167
- results = q.run()
165
+ # q = QuestionBudget.example()
166
+ # results = q.run()
168
167
 
169
168
  import doctest
170
169
 
@@ -9,8 +9,6 @@ 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
14
12
 
15
13
 
16
14
  class QuestionCheckBox(QuestionBase):
@@ -55,13 +53,17 @@ class QuestionCheckBox(QuestionBase):
55
53
  self._validate_answer_checkbox(answer)
56
54
  return answer
57
55
 
58
- def _translate_answer_code_to_answer(self, answer_codes, scenario: Scenario = None):
56
+ def _translate_answer_code_to_answer(
57
+ self, answer_codes, scenario: "Scenario" = None
58
+ ):
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
+
65
67
  scenario = scenario or Scenario()
66
68
  translated_options = [
67
69
  Template(option).render(scenario) for option in self.question_options
@@ -73,6 +75,8 @@ class QuestionCheckBox(QuestionBase):
73
75
 
74
76
  def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
75
77
  """Simulate a valid answer for debugging purposes."""
78
+ from edsl.utilities.utilities import random_string
79
+
76
80
  min_selections = self.min_selections or 1
77
81
  max_selections = self.max_selections or len(self.question_options)
78
82
  num_selections = random.randint(min_selections, max_selections)
@@ -2,8 +2,6 @@ from __future__ import annotations
2
2
  from typing import Any
3
3
  from edsl.questions.QuestionBase import QuestionBase
4
4
  from edsl.questions.descriptors import AnswerTemplateDescriptor
5
- from edsl.scenarios import Scenario
6
- from edsl.utilities import random_string
7
5
 
8
6
 
9
7
  class QuestionExtract(QuestionBase):
@@ -44,12 +42,14 @@ class QuestionExtract(QuestionBase):
44
42
  self._validate_answer_extract(answer)
45
43
  return answer
46
44
 
47
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
45
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
48
46
  """Return the answer in a human-readable format."""
49
47
  return answer
50
48
 
51
49
  def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
52
50
  """Simulate a valid answer for debugging purposes."""
51
+ from edsl.utilities.utilities import random_string
52
+
53
53
  return {
54
54
  "answer": {key: random_string() for key in self.answer_template.keys()},
55
55
  "comment": random_string(),
@@ -106,6 +106,8 @@ def main():
106
106
  q.to_dict()
107
107
  assert q.from_dict(q.to_dict()) == q
108
108
 
109
+
110
+ if __name__ == "__main__":
109
111
  import doctest
110
112
 
111
113
  doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
  import textwrap
3
3
  from typing import Any, Optional
4
+ from uuid import uuid4
4
5
  from edsl.questions.QuestionBase import QuestionBase
5
- from edsl.scenarios import Scenario
6
- from edsl.utilities import random_string
7
6
 
8
7
 
9
8
  class QuestionFreeText(QuestionBase):
@@ -43,12 +42,14 @@ class QuestionFreeText(QuestionBase):
43
42
  self._validate_answer_key_value(answer, "answer", str)
44
43
  return answer
45
44
 
46
- def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
45
+ def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
47
46
  """Do nothing, because the answer is already in a human-readable format."""
48
47
  return answer
49
48
 
50
49
  def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
51
50
  """Simulate a valid answer for debugging purposes."""
51
+ from edsl.utilities.utilities import random_string
52
+
52
53
  return {"answer": random_string()}
53
54
 
54
55
  @property
@@ -65,9 +66,10 @@ class QuestionFreeText(QuestionBase):
65
66
  return question_html_content
66
67
 
67
68
  @classmethod
68
- def example(cls) -> QuestionFreeText:
69
+ def example(cls, randomize: bool = False) -> QuestionFreeText:
69
70
  """Return an example instance of a free text question."""
70
- return cls(question_name="how_are_you", question_text="How are you?")
71
+ addition = "" if not randomize else str(uuid4())
72
+ return cls(question_name="how_are_you", question_text=f"How are you?{addition}")
71
73
 
72
74
 
73
75
  def main():