edsl 0.1.48__py3-none-any.whl → 0.1.50__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 (239) hide show
  1. edsl/__init__.py +124 -53
  2. edsl/__version__.py +1 -1
  3. edsl/agents/agent.py +21 -21
  4. edsl/agents/agent_list.py +2 -5
  5. edsl/agents/exceptions.py +119 -5
  6. edsl/base/__init__.py +10 -35
  7. edsl/base/base_class.py +71 -36
  8. edsl/base/base_exception.py +204 -0
  9. edsl/base/data_transfer_models.py +1 -1
  10. edsl/base/exceptions.py +94 -0
  11. edsl/buckets/__init__.py +15 -1
  12. edsl/buckets/bucket_collection.py +3 -4
  13. edsl/buckets/exceptions.py +75 -0
  14. edsl/buckets/model_buckets.py +1 -2
  15. edsl/buckets/token_bucket.py +11 -6
  16. edsl/buckets/token_bucket_api.py +1 -2
  17. edsl/buckets/token_bucket_client.py +9 -7
  18. edsl/caching/cache.py +7 -2
  19. edsl/caching/cache_entry.py +10 -9
  20. edsl/caching/exceptions.py +113 -7
  21. edsl/caching/remote_cache_sync.py +1 -2
  22. edsl/caching/sql_dict.py +17 -12
  23. edsl/cli.py +43 -0
  24. edsl/config/config_class.py +30 -6
  25. edsl/conversation/Conversation.py +3 -2
  26. edsl/conversation/exceptions.py +58 -0
  27. edsl/conversation/mug_negotiation.py +0 -2
  28. edsl/coop/__init__.py +20 -1
  29. edsl/coop/coop.py +129 -38
  30. edsl/coop/exceptions.py +188 -9
  31. edsl/coop/price_fetcher.py +3 -6
  32. edsl/coop/utils.py +4 -6
  33. edsl/dataset/__init__.py +5 -4
  34. edsl/dataset/dataset.py +53 -43
  35. edsl/dataset/dataset_operations_mixin.py +86 -72
  36. edsl/dataset/dataset_tree.py +9 -5
  37. edsl/dataset/display/table_display.py +0 -2
  38. edsl/dataset/display/table_renderers.py +0 -1
  39. edsl/dataset/exceptions.py +125 -0
  40. edsl/dataset/file_exports.py +18 -11
  41. edsl/dataset/r/ggplot.py +13 -6
  42. edsl/display/__init__.py +27 -0
  43. edsl/display/core.py +147 -0
  44. edsl/display/plugin.py +189 -0
  45. edsl/display/utils.py +52 -0
  46. edsl/inference_services/__init__.py +9 -1
  47. edsl/inference_services/available_model_cache_handler.py +1 -1
  48. edsl/inference_services/available_model_fetcher.py +4 -5
  49. edsl/inference_services/data_structures.py +9 -6
  50. edsl/inference_services/exceptions.py +132 -1
  51. edsl/inference_services/inference_service_abc.py +2 -2
  52. edsl/inference_services/inference_services_collection.py +2 -6
  53. edsl/inference_services/registry.py +4 -3
  54. edsl/inference_services/service_availability.py +2 -1
  55. edsl/inference_services/services/anthropic_service.py +4 -1
  56. edsl/inference_services/services/aws_bedrock.py +13 -12
  57. edsl/inference_services/services/azure_ai.py +12 -10
  58. edsl/inference_services/services/deep_infra_service.py +1 -4
  59. edsl/inference_services/services/deep_seek_service.py +1 -5
  60. edsl/inference_services/services/google_service.py +6 -2
  61. edsl/inference_services/services/groq_service.py +1 -1
  62. edsl/inference_services/services/mistral_ai_service.py +4 -2
  63. edsl/inference_services/services/ollama_service.py +1 -1
  64. edsl/inference_services/services/open_ai_service.py +7 -5
  65. edsl/inference_services/services/perplexity_service.py +6 -2
  66. edsl/inference_services/services/test_service.py +8 -7
  67. edsl/inference_services/services/together_ai_service.py +2 -3
  68. edsl/inference_services/services/xai_service.py +1 -1
  69. edsl/instructions/__init__.py +1 -1
  70. edsl/instructions/change_instruction.py +3 -2
  71. edsl/instructions/exceptions.py +61 -0
  72. edsl/instructions/instruction.py +5 -2
  73. edsl/instructions/instruction_collection.py +2 -1
  74. edsl/instructions/instruction_handler.py +4 -9
  75. edsl/interviews/ReportErrors.py +0 -3
  76. edsl/interviews/__init__.py +9 -2
  77. edsl/interviews/answering_function.py +11 -13
  78. edsl/interviews/exception_tracking.py +14 -7
  79. edsl/interviews/exceptions.py +79 -0
  80. edsl/interviews/interview.py +32 -29
  81. edsl/interviews/interview_status_dictionary.py +4 -2
  82. edsl/interviews/interview_status_log.py +2 -1
  83. edsl/interviews/interview_task_manager.py +3 -3
  84. edsl/interviews/request_token_estimator.py +3 -1
  85. edsl/interviews/statistics.py +2 -3
  86. edsl/invigilators/__init__.py +7 -1
  87. edsl/invigilators/exceptions.py +79 -0
  88. edsl/invigilators/invigilator_base.py +0 -1
  89. edsl/invigilators/invigilators.py +8 -12
  90. edsl/invigilators/prompt_constructor.py +1 -5
  91. edsl/invigilators/prompt_helpers.py +8 -4
  92. edsl/invigilators/question_instructions_prompt_builder.py +1 -1
  93. edsl/invigilators/question_option_processor.py +9 -5
  94. edsl/invigilators/question_template_replacements_builder.py +3 -2
  95. edsl/jobs/__init__.py +3 -3
  96. edsl/jobs/async_interview_runner.py +24 -22
  97. edsl/jobs/check_survey_scenario_compatibility.py +7 -6
  98. edsl/jobs/data_structures.py +7 -4
  99. edsl/jobs/exceptions.py +177 -8
  100. edsl/jobs/fetch_invigilator.py +1 -1
  101. edsl/jobs/jobs.py +72 -67
  102. edsl/jobs/jobs_checks.py +2 -3
  103. edsl/jobs/jobs_component_constructor.py +2 -2
  104. edsl/jobs/jobs_pricing_estimation.py +3 -2
  105. edsl/jobs/jobs_remote_inference_logger.py +5 -4
  106. edsl/jobs/jobs_runner_asyncio.py +1 -2
  107. edsl/jobs/jobs_runner_status.py +8 -9
  108. edsl/jobs/remote_inference.py +26 -23
  109. edsl/jobs/results_exceptions_handler.py +8 -5
  110. edsl/key_management/__init__.py +3 -1
  111. edsl/key_management/exceptions.py +62 -0
  112. edsl/key_management/key_lookup.py +1 -1
  113. edsl/key_management/key_lookup_builder.py +37 -14
  114. edsl/key_management/key_lookup_collection.py +2 -0
  115. edsl/language_models/__init__.py +1 -1
  116. edsl/language_models/exceptions.py +302 -14
  117. edsl/language_models/language_model.py +4 -7
  118. edsl/language_models/model.py +4 -4
  119. edsl/language_models/model_list.py +1 -1
  120. edsl/language_models/price_manager.py +1 -1
  121. edsl/language_models/raw_response_handler.py +14 -9
  122. edsl/language_models/registry.py +17 -21
  123. edsl/language_models/repair.py +0 -6
  124. edsl/language_models/unused/fake_openai_service.py +0 -1
  125. edsl/load_plugins.py +69 -0
  126. edsl/logger.py +146 -0
  127. edsl/notebooks/notebook.py +1 -1
  128. edsl/notebooks/notebook_to_latex.py +0 -1
  129. edsl/plugins/__init__.py +63 -0
  130. edsl/plugins/built_in/export_example.py +50 -0
  131. edsl/plugins/built_in/pig_latin.py +67 -0
  132. edsl/plugins/cli.py +372 -0
  133. edsl/plugins/cli_typer.py +283 -0
  134. edsl/plugins/exceptions.py +31 -0
  135. edsl/plugins/hookspec.py +51 -0
  136. edsl/plugins/plugin_host.py +128 -0
  137. edsl/plugins/plugin_manager.py +633 -0
  138. edsl/plugins/plugins_registry.py +168 -0
  139. edsl/prompts/__init__.py +2 -0
  140. edsl/prompts/exceptions.py +107 -5
  141. edsl/prompts/prompt.py +14 -6
  142. edsl/questions/HTMLQuestion.py +5 -11
  143. edsl/questions/Quick.py +0 -1
  144. edsl/questions/__init__.py +2 -0
  145. edsl/questions/answer_validator_mixin.py +318 -318
  146. edsl/questions/compose_questions.py +2 -2
  147. edsl/questions/descriptors.py +10 -49
  148. edsl/questions/exceptions.py +278 -22
  149. edsl/questions/loop_processor.py +7 -5
  150. edsl/questions/prompt_templates/question_list.jinja +3 -0
  151. edsl/questions/question_base.py +14 -16
  152. edsl/questions/question_base_gen_mixin.py +2 -2
  153. edsl/questions/question_base_prompts_mixin.py +9 -3
  154. edsl/questions/question_budget.py +9 -5
  155. edsl/questions/question_check_box.py +3 -5
  156. edsl/questions/question_dict.py +171 -194
  157. edsl/questions/question_extract.py +1 -1
  158. edsl/questions/question_free_text.py +4 -6
  159. edsl/questions/question_functional.py +4 -3
  160. edsl/questions/question_list.py +36 -9
  161. edsl/questions/question_matrix.py +95 -61
  162. edsl/questions/question_multiple_choice.py +6 -4
  163. edsl/questions/question_numerical.py +2 -4
  164. edsl/questions/question_registry.py +4 -2
  165. edsl/questions/register_questions_meta.py +0 -1
  166. edsl/questions/response_validator_abc.py +7 -13
  167. edsl/questions/templates/dict/answering_instructions.jinja +1 -0
  168. edsl/questions/templates/rank/question_presentation.jinja +1 -1
  169. edsl/results/__init__.py +1 -1
  170. edsl/results/exceptions.py +141 -7
  171. edsl/results/report.py +0 -1
  172. edsl/results/result.py +4 -5
  173. edsl/results/results.py +10 -51
  174. edsl/results/results_selector.py +8 -4
  175. edsl/scenarios/PdfExtractor.py +2 -2
  176. edsl/scenarios/construct_download_link.py +69 -35
  177. edsl/scenarios/directory_scanner.py +33 -14
  178. edsl/scenarios/document_chunker.py +1 -1
  179. edsl/scenarios/exceptions.py +238 -14
  180. edsl/scenarios/file_methods.py +1 -1
  181. edsl/scenarios/file_store.py +7 -3
  182. edsl/scenarios/handlers/__init__.py +17 -0
  183. edsl/scenarios/handlers/docx_file_store.py +0 -5
  184. edsl/scenarios/handlers/pdf_file_store.py +0 -1
  185. edsl/scenarios/handlers/pptx_file_store.py +0 -5
  186. edsl/scenarios/handlers/py_file_store.py +0 -1
  187. edsl/scenarios/handlers/sql_file_store.py +1 -4
  188. edsl/scenarios/handlers/sqlite_file_store.py +0 -1
  189. edsl/scenarios/handlers/txt_file_store.py +1 -1
  190. edsl/scenarios/scenario.py +0 -1
  191. edsl/scenarios/scenario_list.py +152 -18
  192. edsl/scenarios/scenario_list_pdf_tools.py +1 -0
  193. edsl/scenarios/scenario_selector.py +0 -1
  194. edsl/surveys/__init__.py +3 -4
  195. edsl/surveys/dag/__init__.py +4 -2
  196. edsl/surveys/descriptors.py +1 -1
  197. edsl/surveys/edit_survey.py +1 -0
  198. edsl/surveys/exceptions.py +165 -9
  199. edsl/surveys/memory/__init__.py +5 -3
  200. edsl/surveys/memory/memory_management.py +1 -0
  201. edsl/surveys/memory/memory_plan.py +6 -15
  202. edsl/surveys/rules/__init__.py +5 -3
  203. edsl/surveys/rules/rule.py +1 -2
  204. edsl/surveys/rules/rule_collection.py +1 -1
  205. edsl/surveys/survey.py +12 -24
  206. edsl/surveys/survey_export.py +6 -3
  207. edsl/surveys/survey_flow_visualization.py +10 -1
  208. edsl/tasks/__init__.py +2 -0
  209. edsl/tasks/question_task_creator.py +3 -3
  210. edsl/tasks/task_creators.py +1 -3
  211. edsl/tasks/task_history.py +5 -7
  212. edsl/tasks/task_status_log.py +1 -2
  213. edsl/tokens/__init__.py +3 -1
  214. edsl/tokens/token_usage.py +1 -1
  215. edsl/utilities/__init__.py +21 -1
  216. edsl/utilities/decorators.py +1 -2
  217. edsl/utilities/markdown_to_docx.py +2 -2
  218. edsl/utilities/markdown_to_pdf.py +1 -1
  219. edsl/utilities/repair_functions.py +0 -1
  220. edsl/utilities/restricted_python.py +0 -1
  221. edsl/utilities/template_loader.py +2 -3
  222. edsl/utilities/utilities.py +8 -29
  223. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
  224. edsl-0.1.50.dist-info/RECORD +363 -0
  225. edsl-0.1.50.dist-info/entry_points.txt +3 -0
  226. edsl/dataset/smart_objects.py +0 -96
  227. edsl/exceptions/BaseException.py +0 -21
  228. edsl/exceptions/__init__.py +0 -54
  229. edsl/exceptions/configuration.py +0 -16
  230. edsl/exceptions/general.py +0 -34
  231. edsl/study/ObjectEntry.py +0 -173
  232. edsl/study/ProofOfWork.py +0 -113
  233. edsl/study/SnapShot.py +0 -80
  234. edsl/study/Study.py +0 -520
  235. edsl/study/__init__.py +0 -6
  236. edsl/utilities/interface.py +0 -135
  237. edsl-0.1.48.dist-info/RECORD +0 -347
  238. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
  239. {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
- from typing import Dict, Any, Optional, Set, Union, TYPE_CHECKING, Literal
2
+ from typing import Dict, Any, Optional, TYPE_CHECKING
3
3
  from functools import cached_property
4
- import time
5
4
  import logging
6
5
 
7
6
  from ..prompts import Prompt
@@ -20,7 +19,6 @@ if TYPE_CHECKING:
20
19
  from ..agents import Agent
21
20
  from ..language_models import LanguageModel
22
21
  from ..surveys.memory import MemoryPlan
23
- from ..questions import QuestionBase
24
22
  from ..scenarios import Scenario
25
23
 
26
24
  logger = logging.getLogger(__name__)
@@ -438,8 +436,6 @@ class PromptConstructor:
438
436
 
439
437
  def get_prompts(self) -> Dict[str, Any]:
440
438
  """Get the prompts for the question."""
441
- start = time.time()
442
-
443
439
  # Build all the components
444
440
  agent_instructions = self.agent_instructions_prompt
445
441
  agent_persona = self.agent_persona_prompt
@@ -76,19 +76,22 @@ class PromptPlan:
76
76
  system_prompt_order = (system_prompt_order,)
77
77
 
78
78
  if not isinstance(user_prompt_order, tuple):
79
- raise TypeError(
79
+ from edsl.invigilators.exceptions import InvigilatorTypeError
80
+ raise InvigilatorTypeError(
80
81
  f"Expected a tuple, but got {type(user_prompt_order).__name__}"
81
82
  )
82
83
 
83
84
  if not isinstance(system_prompt_order, tuple):
84
- raise TypeError(
85
+ from edsl.invigilators.exceptions import InvigilatorTypeError
86
+ raise InvigilatorTypeError(
85
87
  f"Expected a tuple, but got {type(system_prompt_order).__name__}"
86
88
  )
87
89
 
88
90
  self.user_prompt_order = self._convert_to_enum(user_prompt_order)
89
91
  self.system_prompt_order = self._convert_to_enum(system_prompt_order)
90
92
  if not self._is_valid_plan():
91
- raise ValueError(
93
+ from edsl.invigilators.exceptions import InvigilatorValueError
94
+ raise InvigilatorValueError(
92
95
  "Invalid plan: must contain each value of PromptComponent exactly once."
93
96
  )
94
97
 
@@ -109,7 +112,8 @@ class PromptPlan:
109
112
  # check is valid components passed
110
113
  component_strings = set([pc.value for pc in PromptComponent])
111
114
  if not set(kwargs.keys()) == component_strings:
112
- raise ValueError(
115
+ from edsl.invigilators.exceptions import InvigilatorValueError
116
+ raise InvigilatorValueError(
113
117
  f"Invalid components passed: {set(kwargs.keys())} but expected {PromptComponent}"
114
118
  )
115
119
 
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Set, Any, Union, TYPE_CHECKING
1
+ from typing import Dict, Set, Any, Union, TYPE_CHECKING
2
2
  from warnings import warn
3
3
  import logging
4
4
  from ..prompts import Prompt
@@ -1,5 +1,7 @@
1
- from jinja2 import Environment, meta
2
- from typing import List, Optional, Union
1
+ from jinja2 import Environment
2
+ from typing import List, Union
3
+
4
+ import edsl.scenarios.scenario # noqa: F401
3
5
 
4
6
 
5
7
  def extract_template_variables(ast) -> List[Union[str, tuple]]:
@@ -61,7 +63,7 @@ class QuestionOptionProcessor:
61
63
 
62
64
  return cls(scenario, prior_answers_dict)
63
65
 
64
- def __init__(self, scenario: 'Scenario', prior_answers_dict: dict):
66
+ def __init__(self, scenario: 'edsl.scenarios.scenario.Scenario', prior_answers_dict: dict):
65
67
  # This handles cases where the question has {{ scenario.key }} - eventually
66
68
  # we might not allow 'naked' scenario keys w/o the scenario prefix
67
69
  #new_scenario = scenario.copy()
@@ -106,9 +108,11 @@ class QuestionOptionProcessor:
106
108
  undeclared_variables = extract_template_variables(parsed_content)
107
109
 
108
110
  if not undeclared_variables:
109
- raise ValueError("No variables found in template string")
111
+ from edsl.invigilators.exceptions import InvigilatorValueError
112
+ raise InvigilatorValueError("No variables found in template string")
110
113
  if len(undeclared_variables) > 1:
111
- raise ValueError("Multiple variables found in template string")
114
+ from edsl.invigilators.exceptions import InvigilatorValueError
115
+ raise InvigilatorValueError("Multiple variables found in template string")
112
116
 
113
117
  return undeclared_variables[0]
114
118
 
@@ -156,7 +156,8 @@ class QuestionTemplateReplacementsBuilder:
156
156
  if var == "scenario":
157
157
  # If we find a scenario variable, we need to check for nested references
158
158
  # Create a modified template with just {{ scenario.* }} expressions to isolate them
159
- scenario_template = "".join([
159
+ # Using a template format for reference, not actually used
160
+ _ = "".join([
160
161
  "{% for key, value in scenario.items() %}{{ key }}{% endfor %}"
161
162
  ])
162
163
  try:
@@ -168,7 +169,7 @@ class QuestionTemplateReplacementsBuilder:
168
169
  for key in scenario_refs:
169
170
  if key in scenario_file_keys:
170
171
  question_file_keys.append(key)
171
- except:
172
+ except Exception:
172
173
  # If there's any issue parsing, just continue with what we have
173
174
  pass
174
175
 
edsl/jobs/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from .jobs import Jobs
2
- from .jobs import RunConfig, RunParameters, RunEnvironment
3
- from .remote_inference import JobsRemoteInferenceHandler
4
- from .jobs_runner_status import JobsRunnerStatusBase
2
+ from .jobs import RunConfig, RunParameters, RunEnvironment # noqa: F401
3
+ from .remote_inference import JobsRemoteInferenceHandler # noqa: F401
4
+ from .jobs_runner_status import JobsRunnerStatusBase # noqa: F401
5
5
 
6
6
 
7
7
  __all__ = ["Jobs"]
@@ -14,22 +14,23 @@ from edsl.data_transfer_models import EDSLResultObjectInput
14
14
  from ..results import Result
15
15
  from ..interviews import Interview
16
16
  from ..config import Config
17
+ from .data_structures import RunConfig
18
+
17
19
  config = Config()
18
20
 
19
21
  if TYPE_CHECKING:
20
22
  from ..jobs import Jobs
21
23
 
22
- from .data_structures import RunConfig
23
-
24
24
  @dataclass
25
25
  class InterviewResult:
26
26
  """Container for the result of an interview along with metadata.
27
-
27
+
28
28
  Attributes:
29
29
  result: The Result object containing the interview answers
30
30
  interview: The Interview object used to conduct the interview
31
31
  order: The original position of this interview in the processing queue
32
32
  """
33
+
33
34
  result: Result
34
35
  interview: Interview
35
36
  order: int
@@ -38,10 +39,10 @@ class InterviewResult:
38
39
  class AsyncInterviewRunner:
39
40
  """
40
41
  Runs interviews asynchronously with controlled concurrency.
41
-
42
+
42
43
  This class manages the parallel execution of multiple interviews while
43
44
  respecting concurrency limits and handling errors appropriately.
44
-
45
+
45
46
  Examples:
46
47
  >>> from unittest.mock import MagicMock, AsyncMock
47
48
  >>> mock_jobs = MagicMock()
@@ -52,13 +53,13 @@ class AsyncInterviewRunner:
52
53
  >>> isinstance(runner._initialized, asyncio.Event)
53
54
  True
54
55
  """
55
-
56
+
56
57
  MAX_CONCURRENT = int(config.EDSL_MAX_CONCURRENT_TASKS)
57
58
 
58
59
  def __init__(self, jobs: "Jobs", run_config: RunConfig):
59
60
  """
60
61
  Initialize the AsyncInterviewRunner.
61
-
62
+
62
63
  Args:
63
64
  jobs: The Jobs object that generates interviews
64
65
  run_config: Configuration for running the interviews
@@ -70,13 +71,13 @@ class AsyncInterviewRunner:
70
71
  def _expand_interviews(self) -> Generator["Interview", None, None]:
71
72
  """
72
73
  Create multiple copies of each interview based on the run configuration.
73
-
74
+
74
75
  This method expands interviews for repeated runs and ensures each has
75
76
  the proper cache configuration.
76
-
77
+
77
78
  Yields:
78
79
  Interview objects ready to be conducted
79
-
80
+
80
81
  Examples:
81
82
  >>> from unittest.mock import MagicMock
82
83
  >>> mock_jobs = MagicMock()
@@ -105,16 +106,16 @@ class AsyncInterviewRunner:
105
106
  ) -> Tuple["Result", "Interview"]:
106
107
  """
107
108
  Asynchronously conduct a single interview.
108
-
109
+
109
110
  This method performs the interview and creates a Result object with
110
111
  the extracted answers and model responses.
111
-
112
+
112
113
  Args:
113
114
  interview: The interview to conduct
114
-
115
+
115
116
  Returns:
116
117
  Tuple containing the Result object and the Interview object
117
-
118
+
118
119
  Notes:
119
120
  'extracted_answers' contains the processed and validated answers
120
121
  from the interview, which may differ from the raw model output.
@@ -137,14 +138,14 @@ class AsyncInterviewRunner:
137
138
  ) -> AsyncGenerator[tuple[Result, Interview], None]:
138
139
  """
139
140
  Run all interviews asynchronously and yield results as they complete.
140
-
141
+
141
142
  This method processes interviews in chunks based on MAX_CONCURRENT,
142
143
  maintaining controlled concurrency while yielding results as soon as
143
144
  they become available.
144
-
145
+
145
146
  Yields:
146
147
  Tuples of (Result, Interview) as interviews complete
147
-
148
+
148
149
  Notes:
149
150
  - Uses structured concurrency patterns for proper resource management
150
151
  - Handles exceptions according to the run configuration
@@ -159,11 +160,11 @@ class AsyncInterviewRunner:
159
160
  try:
160
161
  result, interview = await self._conduct_interview(interview)
161
162
  self.run_config.environment.jobs_runner_status.add_completed_interview(
162
- result
163
+ interview
163
164
  )
164
165
  result.order = idx
165
166
  return InterviewResult(result, interview, idx)
166
- except Exception as e:
167
+ except Exception:
167
168
  if self.run_config.parameters.stop_on_exception:
168
169
  raise
169
170
  return None
@@ -187,7 +188,7 @@ class AsyncInterviewRunner:
187
188
  for result in (r for r in results if r is not None):
188
189
  yield result.result, result.interview
189
190
 
190
- except Exception as e:
191
+ except Exception:
191
192
  if self.run_config.parameters.stop_on_exception:
192
193
  raise
193
194
  continue
@@ -200,5 +201,6 @@ class AsyncInterviewRunner:
200
201
 
201
202
 
202
203
  if __name__ == "__main__":
203
- import doctest
204
- doctest.testmod()
204
+ import doctest
205
+
206
+ doctest.testmod()
@@ -2,6 +2,7 @@ import warnings
2
2
  from typing import TYPE_CHECKING
3
3
  from edsl.scenarios import ScenarioList
4
4
  from edsl.surveys import Survey
5
+ from edsl.jobs.exceptions import JobsCompatibilityError
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from edsl.surveys.Survey import Survey
@@ -34,19 +35,19 @@ class CheckSurveyScenarioCompatibility:
34
35
  >>> s = Scenario({'plop': "A", 'poo': "B"})
35
36
  >>> j = Jobs(survey = Survey(questions=[q])).by(s)
36
37
  >>> cs = CheckSurveyScenarioCompatibility(j.survey, j.scenarios)
37
- >>> cs.check(strict = True)
38
+ >>> cs.check(strict = True) # doctest: +ELLIPSIS
38
39
  Traceback (most recent call last):
39
40
  ...
40
- ValueError: The following parameters are in the scenarios but not in the survey: {'plop'}
41
+ edsl.jobs.exceptions.JobsCompatibilityError: The following parameters are in the scenarios but not in the survey: {'plop'}...
41
42
 
42
43
  >>> q = QuestionFreeText(question_text = "Hello", question_name = "ugly_question")
43
44
  >>> s = Scenario({'ugly_question': "B"})
44
45
  >>> from edsl.scenarios import ScenarioList
45
46
  >>> cs = CheckSurveyScenarioCompatibility(Survey(questions=[q]), ScenarioList([s]))
46
- >>> cs.check()
47
+ >>> cs.check() # doctest: +ELLIPSIS
47
48
  Traceback (most recent call last):
48
49
  ...
49
- ValueError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues.
50
+ edsl.jobs.exceptions.JobsCompatibilityError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues...
50
51
  """
51
52
  survey_parameters: set = self.survey.parameters
52
53
  scenario_parameters: set = self.scenarios.parameters
@@ -59,7 +60,7 @@ class CheckSurveyScenarioCompatibility:
59
60
  ):
60
61
  msg0 = f"The following names are in both the survey question_names and the scenario keys: {intersection}. This will create issues."
61
62
 
62
- raise ValueError(msg0)
63
+ raise JobsCompatibilityError(msg0)
63
64
 
64
65
  if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
65
66
  msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
@@ -69,7 +70,7 @@ class CheckSurveyScenarioCompatibility:
69
70
  if msg1 or msg2:
70
71
  message = "\n".join(filter(None, [msg1, msg2]))
71
72
  if strict:
72
- raise ValueError(message)
73
+ raise JobsCompatibilityError(message)
73
74
  else:
74
75
  if warn:
75
76
  warnings.warn(message)
@@ -1,5 +1,7 @@
1
- from typing import Optional, Literal
1
+ from typing import Optional, Literal, TYPE_CHECKING
2
2
  from dataclasses import dataclass, asdict
3
+ from collections import UserDict
4
+ from edsl.data_transfer_models import EDSLResultObjectInput
3
5
 
4
6
  # from edsl.data_transfer_models import VisibilityType
5
7
  from ..caching import Cache
@@ -9,6 +11,10 @@ from ..base import Base
9
11
 
10
12
  from .jobs_runner_status import JobsRunnerStatus
11
13
 
14
+ if TYPE_CHECKING:
15
+ from ..questions.question_base import QuestionBase
16
+ from ..surveys import Survey
17
+
12
18
  VisibilityType = Literal["private", "public", "unlisted"]
13
19
 
14
20
  @dataclass
@@ -157,9 +163,6 @@ class RunConfig:
157
163
  Additional data structures for working with job results and answers.
158
164
  """
159
165
 
160
- from collections import UserDict
161
- from edsl.data_transfer_models import EDSLResultObjectInput
162
-
163
166
 
164
167
  class Answers(UserDict):
165
168
  """
edsl/jobs/exceptions.py CHANGED
@@ -1,26 +1,195 @@
1
- from textwrap import dedent
2
1
 
3
2
  from ..base import BaseException
4
3
 
5
4
  class JobsErrors(BaseException):
6
- pass
5
+ """
6
+ Base exception class for all job-related errors.
7
+
8
+ This is the parent class for all exceptions related to job execution
9
+ in the EDSL framework. It provides a common type for catching any
10
+ job-specific error.
11
+ """
12
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/jobs.html"
7
13
 
8
14
 
9
15
  class JobsRunError(JobsErrors):
10
- pass
16
+ """
17
+ Exception raised when a job fails to run correctly.
18
+
19
+ This exception indicates issues with job execution such as:
20
+ - Configuration problems before job execution
21
+ - Resource allocation failures
22
+ - Job termination due to internal errors
23
+
24
+ To fix this error:
25
+ 1. Check the job configuration for any invalid parameters
26
+ 2. Ensure all required resources (models, agents, etc.) are available
27
+ 3. Verify that dependent services are accessible
28
+
29
+ Note: This exception is currently not used actively in the codebase,
30
+ but is kept for potential future use and test cases.
31
+ """
32
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/jobs.html"
11
33
 
12
34
 
13
35
  class MissingRemoteInferenceError(JobsErrors):
14
- pass
36
+ """
37
+ Exception raised when remote inference is required but not configured.
38
+
39
+ This exception occurs when:
40
+ - A job requires remote inference capabilities but they're not available
41
+ - Credentials for remote inference are missing or invalid
42
+
43
+ To fix this error:
44
+ 1. Set up remote inference configuration in your environment
45
+ 2. Provide valid API keys for the required inference service
46
+
47
+ Note: This exception is defined but not currently used in the codebase.
48
+ It raises Exception("not used") to indicate this state.
49
+ """
50
+ def __init__(self, message="Remote inference configuration is missing", **kwargs):
51
+ super().__init__(message, **kwargs)
15
52
 
16
53
 
17
- class InterviewError(Exception):
18
- pass
54
+ class InterviewError(JobsErrors):
55
+ """
56
+ Base exception class for all interview-related errors.
57
+
58
+ This exception serves as the parent class for specific interview errors
59
+ and handles cases where an interview process fails for reasons not covered
60
+ by more specific exceptions.
61
+ """
62
+
63
+ def __init__(self, message="An error occurred during the interview process", **kwargs):
64
+ super().__init__(message, **kwargs)
65
+ self.message = message
19
66
 
20
67
 
21
68
  class InterviewErrorPriorTaskCanceled(InterviewError):
22
- pass
69
+ """
70
+ Exception raised when a task cannot run because a dependent task failed.
71
+
72
+ This exception is raised in task pipelines when a prerequisite task
73
+ fails or is canceled, preventing dependent tasks from executing.
74
+
75
+ When you encounter this error:
76
+ 1. Check the error logs for information about the failed dependent task
77
+ 2. Fix any issues with the prerequisite task first before retrying
78
+ 3. If using custom task dependencies, verify that the dependency chain is correct
79
+ """
80
+
81
+ def __init__(self, message="Cannot run this task because a required prior task was canceled or failed"):
82
+ super().__init__(message)
23
83
 
24
84
 
25
85
  class InterviewTimeoutError(InterviewError):
26
- pass
86
+ """
87
+ Exception raised when an interview operation times out.
88
+
89
+ This exception indicates that a model call or other operation
90
+ during an interview process took too long to complete.
91
+
92
+ To fix this error:
93
+ 1. Check your network connection if using remote models
94
+ 2. Consider increasing timeout settings if dealing with complex prompts
95
+ 3. Try a different model provider if consistently experiencing timeouts
96
+
97
+ Note: While defined here, the codebase currently uses LanguageModelNoResponseError
98
+ to handle timeouts in actual operation.
99
+ """
100
+
101
+ def __init__(self, message="The interview operation timed out", **kwargs):
102
+ super().__init__(message, **kwargs)
103
+
104
+
105
+ class JobsValueError(JobsErrors):
106
+ """
107
+ Exception raised when there's an invalid value in job-related operations.
108
+
109
+ This exception indicates that a parameter or value used in job configuration
110
+ or execution is invalid, out of range, or otherwise inappropriate.
111
+
112
+ Common causes include:
113
+ - Invalid question names in job configuration
114
+ - Incompatible survey and scenario combinations
115
+ - Invalid parameter values for job construction
116
+
117
+ To fix this error:
118
+ 1. Check the parameter values in your job configuration
119
+ 2. Ensure that all question names exist in the survey
120
+ 3. Verify that survey and scenario combinations are compatible
121
+ """
122
+
123
+ def __init__(self, message="Invalid value in jobs module", **kwargs):
124
+ super().__init__(message, **kwargs)
125
+
126
+
127
+ class JobsCompatibilityError(JobsErrors):
128
+ """
129
+ Exception raised when there are compatibility issues between components.
130
+
131
+ This exception indicates that the components being used together (like
132
+ surveys and scenarios) are not compatible with each other for the requested
133
+ operation.
134
+
135
+ To fix this error:
136
+ 1. Check that your survey and scenario are compatible
137
+ 2. Ensure all referenced questions exist in the survey
138
+ 3. Verify that scenario fields match expected inputs for questions
139
+ """
140
+
141
+ def __init__(self, message="Compatibility issue between job components", **kwargs):
142
+ super().__init__(message, **kwargs)
143
+
144
+
145
+ class JobsImplementationError(JobsErrors):
146
+ """
147
+ Exception raised when a required method or feature is not implemented.
148
+
149
+ This exception indicates that a method or functionality expected to be
150
+ available is not implemented, typically in abstract classes or
151
+ interfaces that require concrete implementation.
152
+
153
+ To fix this error:
154
+ 1. Implement the required method in your subclass
155
+ 2. Use a different implementation that provides the required functionality
156
+ 3. Check for updates to the library that might implement this feature
157
+ """
158
+
159
+ def __init__(self, message="Required method or feature is not implemented", **kwargs):
160
+ super().__init__(message, **kwargs)
161
+
162
+
163
+ class RemoteInferenceError(JobsErrors):
164
+ """
165
+ Exception raised when there are issues with remote inference.
166
+
167
+ This exception indicates problems with remote inference configuration,
168
+ connection, or execution. It can occur when a remote job fails to create
169
+ or execute properly.
170
+
171
+ To fix this error:
172
+ 1. Check your remote inference configuration
173
+ 2. Verify API keys and authentication are correct
174
+ 3. Ensure the remote service is available and responsive
175
+ """
176
+
177
+ def __init__(self, message="Remote inference operation failed", **kwargs):
178
+ super().__init__(message, **kwargs)
179
+
180
+
181
+ class JobsTypeError(JobsErrors):
182
+ """
183
+ Exception raised when there's a type mismatch in job-related operations.
184
+
185
+ This exception indicates that a parameter or value is of the wrong type
186
+ for the operation being performed.
187
+
188
+ To fix this error:
189
+ 1. Check the types of parameters you're passing to job functions
190
+ 2. Ensure that you're using the correct types as defined in the API
191
+ 3. Convert parameters to the expected types if necessary
192
+ """
193
+
194
+ def __init__(self, message="Type mismatch in jobs module", **kwargs):
195
+ super().__init__(message, **kwargs)
@@ -1,4 +1,4 @@
1
- from typing import List, Dict, Any, Optional, TYPE_CHECKING
1
+ from typing import Dict, Any, Optional, TYPE_CHECKING
2
2
 
3
3
  if TYPE_CHECKING:
4
4
  from ..questions import QuestionBase