edsl 0.1.50__py3-none-any.whl → 0.1.52__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 (119) hide show
  1. edsl/__init__.py +45 -34
  2. edsl/__version__.py +1 -1
  3. edsl/base/base_exception.py +2 -2
  4. edsl/buckets/bucket_collection.py +1 -1
  5. edsl/buckets/exceptions.py +32 -0
  6. edsl/buckets/token_bucket_api.py +26 -10
  7. edsl/caching/cache.py +5 -2
  8. edsl/caching/remote_cache_sync.py +5 -5
  9. edsl/caching/sql_dict.py +12 -11
  10. edsl/config/__init__.py +1 -1
  11. edsl/config/config_class.py +4 -2
  12. edsl/conversation/Conversation.py +9 -5
  13. edsl/conversation/car_buying.py +1 -3
  14. edsl/conversation/mug_negotiation.py +2 -6
  15. edsl/coop/__init__.py +11 -8
  16. edsl/coop/coop.py +15 -13
  17. edsl/coop/coop_functions.py +1 -1
  18. edsl/coop/ep_key_handling.py +1 -1
  19. edsl/coop/price_fetcher.py +2 -2
  20. edsl/coop/utils.py +2 -2
  21. edsl/dataset/dataset.py +144 -63
  22. edsl/dataset/dataset_operations_mixin.py +14 -6
  23. edsl/dataset/dataset_tree.py +3 -3
  24. edsl/dataset/display/table_renderers.py +6 -3
  25. edsl/dataset/file_exports.py +4 -4
  26. edsl/dataset/r/ggplot.py +3 -3
  27. edsl/inference_services/available_model_fetcher.py +2 -2
  28. edsl/inference_services/data_structures.py +5 -5
  29. edsl/inference_services/inference_service_abc.py +1 -1
  30. edsl/inference_services/inference_services_collection.py +1 -1
  31. edsl/inference_services/service_availability.py +3 -3
  32. edsl/inference_services/services/azure_ai.py +3 -3
  33. edsl/inference_services/services/google_service.py +1 -1
  34. edsl/inference_services/services/test_service.py +1 -1
  35. edsl/instructions/change_instruction.py +5 -4
  36. edsl/instructions/instruction.py +1 -0
  37. edsl/instructions/instruction_collection.py +5 -4
  38. edsl/instructions/instruction_handler.py +10 -8
  39. edsl/interviews/answering_function.py +20 -21
  40. edsl/interviews/exception_tracking.py +3 -2
  41. edsl/interviews/interview.py +1 -1
  42. edsl/interviews/interview_status_dictionary.py +1 -1
  43. edsl/interviews/interview_task_manager.py +7 -4
  44. edsl/interviews/request_token_estimator.py +3 -2
  45. edsl/interviews/statistics.py +2 -2
  46. edsl/invigilators/invigilators.py +34 -6
  47. edsl/jobs/__init__.py +39 -2
  48. edsl/jobs/async_interview_runner.py +1 -1
  49. edsl/jobs/check_survey_scenario_compatibility.py +5 -5
  50. edsl/jobs/data_structures.py +2 -2
  51. edsl/jobs/html_table_job_logger.py +494 -257
  52. edsl/jobs/jobs.py +2 -2
  53. edsl/jobs/jobs_checks.py +5 -5
  54. edsl/jobs/jobs_component_constructor.py +2 -2
  55. edsl/jobs/jobs_pricing_estimation.py +1 -1
  56. edsl/jobs/jobs_runner_asyncio.py +2 -2
  57. edsl/jobs/jobs_status_enums.py +1 -0
  58. edsl/jobs/remote_inference.py +47 -13
  59. edsl/jobs/results_exceptions_handler.py +2 -2
  60. edsl/language_models/language_model.py +151 -145
  61. edsl/notebooks/__init__.py +24 -1
  62. edsl/notebooks/exceptions.py +82 -0
  63. edsl/notebooks/notebook.py +7 -3
  64. edsl/notebooks/notebook_to_latex.py +1 -1
  65. edsl/prompts/__init__.py +23 -2
  66. edsl/prompts/prompt.py +1 -1
  67. edsl/questions/__init__.py +4 -4
  68. edsl/questions/answer_validator_mixin.py +0 -5
  69. edsl/questions/compose_questions.py +2 -2
  70. edsl/questions/descriptors.py +1 -1
  71. edsl/questions/question_base.py +32 -3
  72. edsl/questions/question_base_prompts_mixin.py +4 -4
  73. edsl/questions/question_budget.py +503 -102
  74. edsl/questions/question_check_box.py +658 -156
  75. edsl/questions/question_dict.py +176 -2
  76. edsl/questions/question_extract.py +401 -61
  77. edsl/questions/question_free_text.py +77 -9
  78. edsl/questions/question_functional.py +118 -9
  79. edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
  80. edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
  81. edsl/questions/question_list.py +246 -26
  82. edsl/questions/question_matrix.py +586 -73
  83. edsl/questions/question_multiple_choice.py +213 -47
  84. edsl/questions/question_numerical.py +360 -29
  85. edsl/questions/question_rank.py +401 -124
  86. edsl/questions/question_registry.py +3 -3
  87. edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
  88. edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
  89. edsl/questions/register_questions_meta.py +2 -1
  90. edsl/questions/response_validator_abc.py +6 -2
  91. edsl/questions/response_validator_factory.py +10 -12
  92. edsl/results/report.py +1 -1
  93. edsl/results/result.py +7 -4
  94. edsl/results/results.py +500 -271
  95. edsl/results/results_selector.py +2 -2
  96. edsl/scenarios/construct_download_link.py +3 -3
  97. edsl/scenarios/scenario.py +1 -2
  98. edsl/scenarios/scenario_list.py +41 -23
  99. edsl/surveys/survey_css.py +3 -3
  100. edsl/surveys/survey_simulator.py +2 -1
  101. edsl/tasks/__init__.py +22 -2
  102. edsl/tasks/exceptions.py +72 -0
  103. edsl/tasks/task_history.py +48 -11
  104. edsl/templates/error_reporting/base.html +37 -4
  105. edsl/templates/error_reporting/exceptions_table.html +105 -33
  106. edsl/templates/error_reporting/interview_details.html +130 -126
  107. edsl/templates/error_reporting/overview.html +21 -25
  108. edsl/templates/error_reporting/report.css +215 -46
  109. edsl/templates/error_reporting/report.js +122 -20
  110. edsl/tokens/__init__.py +27 -1
  111. edsl/tokens/exceptions.py +37 -0
  112. edsl/tokens/interview_token_usage.py +3 -2
  113. edsl/tokens/token_usage.py +4 -3
  114. {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/METADATA +1 -1
  115. {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/RECORD +118 -116
  116. edsl/questions/derived/__init__.py +0 -0
  117. {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/LICENSE +0 -0
  118. {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/WHEEL +0 -0
  119. {edsl-0.1.50.dist-info → edsl-0.1.52.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,26 @@
1
+ """
2
+ The notebooks module provides tools for working with Jupyter notebooks.
3
+
4
+ It includes classes for notebook creation, manipulation, and conversion
5
+ to other formats such as LaTeX.
6
+ """
7
+
1
8
  from .notebook import Notebook
9
+ from .notebook_to_latex import NotebookToLaTeX
10
+ from .exceptions import (
11
+ NotebookError,
12
+ NotebookValueError,
13
+ NotebookFormatError,
14
+ NotebookConversionError,
15
+ NotebookEnvironmentError,
16
+ )
2
17
 
3
- __all__ = ["Notebook"]
18
+ __all__ = [
19
+ "Notebook",
20
+ "NotebookToLaTeX",
21
+ "NotebookError",
22
+ "NotebookValueError",
23
+ "NotebookFormatError",
24
+ "NotebookConversionError",
25
+ "NotebookEnvironmentError",
26
+ ]
@@ -0,0 +1,82 @@
1
+ """
2
+ Custom exceptions for the notebooks module.
3
+ """
4
+
5
+ from ..base import BaseException
6
+
7
+
8
+ class NotebookError(BaseException):
9
+ """
10
+ Base exception class for all notebook-related errors.
11
+
12
+ This is the parent class for all exceptions related to notebook
13
+ operations, including creation, validation, and conversion.
14
+ """
15
+ relevant_doc = "https://docs.expectedparrot.com/"
16
+
17
+
18
+ class NotebookValueError(NotebookError):
19
+ """
20
+ Exception raised when an invalid value is provided to a notebook method.
21
+
22
+ This exception occurs when attempting to create or modify a notebook
23
+ with invalid values, such as:
24
+ - Invalid data format
25
+ - Incompatible notebook contents
26
+
27
+ Examples:
28
+ ```python
29
+ # Attempting to create a notebook with invalid data
30
+ notebook = Notebook(data=invalid_data) # Raises NotebookValueError
31
+ ```
32
+ """
33
+ relevant_doc = "https://docs.expectedparrot.com/"
34
+
35
+
36
+ class NotebookFormatError(NotebookError):
37
+ """
38
+ Exception raised when a notebook's format is invalid.
39
+
40
+ This exception occurs when the notebook structure does not conform
41
+ to the expected Jupyter Notebook format.
42
+
43
+ Examples:
44
+ ```python
45
+ # Attempting to load a notebook with invalid format
46
+ notebook = Notebook.from_dict(invalid_dict) # Raises NotebookFormatError
47
+ ```
48
+ """
49
+ relevant_doc = "https://docs.expectedparrot.com/"
50
+
51
+
52
+ class NotebookConversionError(NotebookError):
53
+ """
54
+ Exception raised when a notebook conversion fails.
55
+
56
+ This exception occurs when attempting to convert a notebook to another
57
+ format (like LaTeX) and the conversion process fails.
58
+
59
+ Examples:
60
+ ```python
61
+ # Attempting to convert a notebook to LaTeX with invalid options
62
+ notebook.to_latex(invalid_options) # Raises NotebookConversionError
63
+ ```
64
+ """
65
+ relevant_doc = "https://docs.expectedparrot.com/"
66
+
67
+
68
+ class NotebookEnvironmentError(NotebookError):
69
+ """
70
+ Exception raised when the notebook environment is not supportable.
71
+
72
+ This exception occurs when attempting to create a notebook in an
73
+ environment that doesn't provide required context, such as when
74
+ trying to create a notebook from within itself outside of VS Code.
75
+
76
+ Examples:
77
+ ```python
78
+ # Attempting to create a notebook from within itself in an unsupported IDE
79
+ notebook = Notebook() # Raises NotebookEnvironmentError
80
+ ```
81
+ """
82
+ relevant_doc = "https://docs.expectedparrot.com/"
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  import json
5
- from typing import Dict, List, Optional
5
+ from typing import Dict, List, Optional, TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from rich.table import Table
6
9
  from uuid import uuid4
7
10
 
8
11
  from ..base import Base
@@ -47,7 +50,8 @@ class Notebook(Base):
47
50
  self.data = json.loads(json.dumps(data))
48
51
  else:
49
52
  # TODO: Support for IDEs other than VSCode
50
- raise NotImplementedError(
53
+ from .exceptions import NotebookEnvironmentError
54
+ raise NotebookEnvironmentError(
51
55
  "Cannot create a notebook from within itself in this development environment"
52
56
  )
53
57
 
@@ -251,7 +255,7 @@ class Notebook(Base):
251
255
 
252
256
  :param filename: Name of the output folder and main tex file (without extension)
253
257
  """
254
- from .NotebookToLaTeX import NotebookToLaTeX
258
+ from .notebook_to_latex import NotebookToLaTeX
255
259
 
256
260
  NotebookToLaTeX(self).convert(filename)
257
261
 
@@ -122,7 +122,7 @@ To clean up build files:
122
122
 
123
123
  # Example usage:
124
124
  if __name__ == "__main__":
125
- from edsl import Notebook
125
+ from .. import Notebook
126
126
 
127
127
  # Create or load a notebook
128
128
  notebook = Notebook.example()
edsl/prompts/__init__.py CHANGED
@@ -1,4 +1,25 @@
1
- # from edsl.prompts.registry import get_classes
1
+ """
2
+ The prompts module provides tools for creating and managing prompts.
3
+
4
+ It includes classes for template-based prompts with variable substitution,
5
+ prompt rendering, and component management for language model interactions.
6
+ """
2
7
  from .prompt import Prompt
8
+ from .exceptions import (
9
+ PromptError,
10
+ TemplateRenderError,
11
+ PromptBadQuestionTypeError,
12
+ PromptBadLanguageModelTypeError,
13
+ PromptValueError,
14
+ PromptImplementationError
15
+ )
3
16
 
4
- __all__ = ["Prompt"]
17
+ __all__ = [
18
+ "Prompt",
19
+ "PromptError",
20
+ "TemplateRenderError",
21
+ "PromptBadQuestionTypeError",
22
+ "PromptBadLanguageModelTypeError",
23
+ "PromptValueError",
24
+ "PromptImplementationError"
25
+ ]
edsl/prompts/prompt.py CHANGED
@@ -145,7 +145,7 @@ class Prompt(PersistenceMixin, RepresentationMixin):
145
145
  if path_to_folder is None:
146
146
  from importlib import resources
147
147
 
148
- path_to_folder = resources.path("edsl.questions", "prompt_templates")
148
+ path_to_folder = resources.path("..questions", "prompt_templates")
149
149
 
150
150
  try:
151
151
  folder_path = Path(path_to_folder)
@@ -120,10 +120,10 @@ from .question_budget import QuestionBudget
120
120
  from .question_rank import QuestionRank
121
121
 
122
122
  # Questions derived from core questions
123
- from .derived.question_likert_five import QuestionLikertFive
124
- from .derived.question_linear_scale import QuestionLinearScale
125
- from .derived.question_yes_no import QuestionYesNo
126
- from .derived.question_top_k import QuestionTopK
123
+ from .question_likert_five import QuestionLikertFive
124
+ from .question_linear_scale import QuestionLinearScale
125
+ from .question_yes_no import QuestionYesNo
126
+ from .question_top_k import QuestionTopK
127
127
 
128
128
  from .exceptions import QuestionScenarioRenderError
129
129
 
@@ -1,10 +1,5 @@
1
1
  """Mixin with validators for LLM answers to questions."""
2
2
 
3
- import re
4
- from typing import Any, Type, Union
5
- from .exceptions import (
6
- QuestionAnswerValidationError,
7
- )
8
3
 
9
4
 
10
5
  class AnswerValidatorMixin:
@@ -3,6 +3,7 @@
3
3
  from .question_functional import QuestionFunctional
4
4
  from .question_base import QuestionBase
5
5
  from ..scenarios import Scenario
6
+ from .exceptions import QuestionValueError
6
7
 
7
8
 
8
9
  def compose_questions(
@@ -19,7 +20,6 @@ def compose_questions(
19
20
  if question_name is None:
20
21
  question_name = f"{q1.question_name}_{q2.question_name}"
21
22
  if q1.question_name not in q2.question_text:
22
- from .exceptions import QuestionValueError
23
23
  raise QuestionValueError(
24
24
  f"q2 requires a field not present in q1's answer. "
25
25
  f"q1: {q1.question_name}, q2: {q2.question_name}"
@@ -30,7 +30,7 @@ def compose_questions(
30
30
  ) -> QuestionFunctional:
31
31
  """Return the answer to the second question given the answer to the first question."""
32
32
  # get the answer to the first question
33
- from edsl.agents.Agent import Agent
33
+ from ..agents.agent import Agent
34
34
 
35
35
  first_answer = (
36
36
  q1.by(scenario)
@@ -230,7 +230,7 @@ class QuestionNameDescriptor(BaseDescriptor):
230
230
 
231
231
  def validate(self, value, instance):
232
232
  """Validate the value is a valid variable name."""
233
- from edsl.utilities.utilities import is_valid_variable_name
233
+ from ..utilities.utilities import is_valid_variable_name
234
234
 
235
235
  if "{{" in value and "}}" in value:
236
236
  # they're trying to use a dynamic question name - let's let this play out
@@ -271,8 +271,25 @@ class QuestionBase(
271
271
  """
272
272
  if not hasattr(self, "_fake_data_factory"):
273
273
  from polyfactory.factories.pydantic_factory import ModelFactory
274
-
275
- class FakeData(ModelFactory[self.response_model]): ...
274
+ from random import randint, uniform
275
+
276
+ class FakeData(ModelFactory[self.response_model]):
277
+ # Add customization for specific question types
278
+ if hasattr(self, 'question_type') and self.question_type == 'numerical':
279
+ @classmethod
280
+ def build_answer(cls):
281
+ min_val = getattr(self, 'min_value', None)
282
+ max_val = getattr(self, 'max_value', None)
283
+
284
+ # Default values if none provided
285
+ min_val = 0 if min_val is None else min_val
286
+ max_val = 100 if max_val is None else max_val
287
+
288
+ # Ensure values are within bounds
289
+ if isinstance(min_val, int) and isinstance(max_val, int):
290
+ return randint(min_val, max_val)
291
+ else:
292
+ return uniform(min_val, max_val)
276
293
 
277
294
  self._fake_data_factory = FakeData
278
295
  return self._fake_data_factory
@@ -358,7 +375,19 @@ class QuestionBase(
358
375
  >>> Q.example()._validate_answer({'answer': 'Hello', 'generated_tokens': 'Hello'})
359
376
  {'answer': 'Hello', 'generated_tokens': 'Hello'}
360
377
  """
361
- return self.response_validator.validate(answer, replacement_dict)
378
+ try:
379
+ return self.response_validator.validate(answer, replacement_dict)
380
+ except Exception as e:
381
+ # Ensure all validation errors are raised as QuestionAnswerValidationError
382
+ from .exceptions import QuestionAnswerValidationError
383
+ if not isinstance(e, QuestionAnswerValidationError):
384
+ raise QuestionAnswerValidationError(
385
+ message=f"Invalid response: {e}",
386
+ data=answer,
387
+ model=getattr(self, 'response_model', None),
388
+ pydantic_error=e if hasattr(e, 'errors') else None
389
+ ) from e
390
+ raise
362
391
 
363
392
  @property
364
393
  def name(self) -> str:
@@ -74,7 +74,7 @@ class QuestionBasePromptsMixin:
74
74
  >>> q.get_instructions(model = "gpt3")
75
75
  Prompt(text=\"""{{question_text}}. Answer in valid JSON like so {'answer': 'comment: <>}\""")
76
76
  """
77
- from edsl.language_models.model import Model
77
+ from ..language_models.model import Model
78
78
 
79
79
  if not hasattr(self, "_model_instructions"):
80
80
  self._model_instructions = {}
@@ -125,7 +125,7 @@ class QuestionBasePromptsMixin:
125
125
  template_text = template_manager.get_template(
126
126
  cls.question_type, "answering_instructions.jinja"
127
127
  )
128
- from edsl.prompts import Prompt
128
+ from ..prompts import Prompt
129
129
 
130
130
  return Prompt(text=template_text)
131
131
 
@@ -134,7 +134,7 @@ class QuestionBasePromptsMixin:
134
134
  template_text = template_manager.get_template(
135
135
  cls.question_type, "question_presentation.jinja"
136
136
  )
137
- from edsl.prompts import Prompt
137
+ from ..prompts import Prompt
138
138
 
139
139
  return Prompt(text=template_text)
140
140
 
@@ -190,7 +190,7 @@ class QuestionBasePromptsMixin:
190
190
  @property
191
191
  def new_default_instructions(self) -> "Prompt":
192
192
  "This is set up as a property because there are mutable question values that determine how it is rendered."
193
- from edsl.prompts import Prompt
193
+ from ..prompts import Prompt
194
194
 
195
195
  return Prompt(self.question_presentation) + Prompt(self.answering_instructions)
196
196