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
edsl/jobs/jobs.py CHANGED
@@ -17,19 +17,14 @@ who need to run complex simulations with language models.
17
17
  """
18
18
  from __future__ import annotations
19
19
  import asyncio
20
- from inspect import signature
21
20
  from typing import Optional, Union, TypeVar, Callable, cast
22
21
  from functools import wraps
23
22
 
24
23
  from typing import (
25
24
  Literal,
26
- Optional,
27
- Union,
28
25
  Sequence,
29
26
  Generator,
30
27
  TYPE_CHECKING,
31
- Callable,
32
- Tuple,
33
28
  )
34
29
 
35
30
  from ..base import Base
@@ -40,6 +35,7 @@ from ..buckets import BucketCollection
40
35
  from ..scenarios import Scenario, ScenarioList
41
36
  from ..surveys import Survey
42
37
  from ..interviews import Interview
38
+ from .exceptions import JobsValueError, JobsImplementationError
43
39
 
44
40
  from .jobs_pricing_estimation import JobsPrompts
45
41
  from .remote_inference import JobsRemoteInferenceHandler
@@ -73,27 +69,25 @@ P = ParamSpec("P")
73
69
  T = TypeVar("T")
74
70
 
75
71
 
76
-
77
-
78
72
  def with_config(f: Callable[P, T]) -> Callable[P, T]:
79
73
  """
80
74
  Decorator that processes function parameters to match the RunConfig dataclass structure.
81
-
75
+
82
76
  This decorator is used primarily with the run() and run_async() methods to provide
83
77
  a consistent interface for job configuration while maintaining a clean API.
84
-
78
+
85
79
  The decorator:
86
80
  1. Extracts environment-related parameters into a RunEnvironment instance
87
81
  2. Extracts execution-related parameters into a RunParameters instance
88
82
  3. Combines both into a single RunConfig object
89
83
  4. Passes this RunConfig to the decorated function as a keyword argument
90
-
84
+
91
85
  Parameters:
92
86
  f (Callable): The function to decorate, typically run() or run_async()
93
-
87
+
94
88
  Returns:
95
89
  Callable: A wrapped function that accepts all RunConfig parameters directly
96
-
90
+
97
91
  Example:
98
92
  @with_config
99
93
  def run(self, *, config: RunConfig) -> Results:
@@ -107,7 +101,8 @@ def with_config(f: Callable[P, T]) -> Callable[P, T]:
107
101
  name: field.default
108
102
  for name, field in RunEnvironment.__dataclass_fields__.items()
109
103
  }
110
- combined = {**parameter_fields, **environment_fields}
104
+ # Combined fields dict used for reference during development
105
+ # combined = {**parameter_fields, **environment_fields}
111
106
 
112
107
  @wraps(f)
113
108
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
@@ -126,25 +121,25 @@ def with_config(f: Callable[P, T]) -> Callable[P, T]:
126
121
  class Jobs(Base):
127
122
  """
128
123
  A collection of agents, scenarios, models, and a survey that orchestrates interviews.
129
-
124
+
130
125
  The Jobs class is the central component for running large-scale experiments or simulations
131
126
  in EDSL. It manages the execution of interviews where agents interact with surveys through
132
127
  language models, possibly in different scenarios.
133
-
128
+
134
129
  Key responsibilities:
135
130
  1. Managing collections of agents, scenarios, and models
136
131
  2. Configuring execution parameters (caching, API keys, etc.)
137
132
  3. Managing parallel execution of interviews
138
133
  4. Handling remote cache and inference capabilities
139
134
  5. Collecting and organizing results
140
-
135
+
141
136
  A typical workflow involves:
142
137
  1. Creating a survey with questions
143
138
  2. Creating a Jobs instance with that survey
144
139
  3. Adding agents, scenarios, and models using the `by()` method
145
140
  4. Running the job with `run()` or `run_async()`
146
141
  5. Analyzing the results
147
-
142
+
148
143
  Jobs implements a fluent interface pattern, where methods return self to allow
149
144
  method chaining for concise, readable configuration.
150
145
  """
@@ -159,22 +154,22 @@ class Jobs(Base):
159
154
  scenarios: Optional[Union["ScenarioList", list["Scenario"]]] = None,
160
155
  ):
161
156
  """Initialize a Jobs instance with a survey and optional components.
162
-
157
+
163
158
  The Jobs constructor requires a survey and optionally accepts collections of
164
159
  agents, models, and scenarios. If any of these optional components are not provided,
165
160
  they can be added later using the `by()` method or will be automatically populated
166
161
  with defaults when the job is run.
167
-
162
+
168
163
  Parameters:
169
164
  survey (Survey): The survey containing questions to be used in the job
170
165
  agents (Union[list[Agent], AgentList], optional): The agents that will take the survey
171
166
  models (Union[ModelList, list[LanguageModel]], optional): The language models to use
172
167
  scenarios (Union[ScenarioList, list[Scenario]], optional): The scenarios to run
173
-
168
+
174
169
  Raises:
175
170
  ValueError: If the survey contains questions with invalid names
176
171
  (e.g., names containing template variables)
177
-
172
+
178
173
  Examples:
179
174
  >>> from edsl.surveys import Survey
180
175
  >>> from edsl.questions import QuestionFreeText
@@ -183,11 +178,7 @@ class Jobs(Base):
183
178
  >>> j = Jobs(survey = s)
184
179
  >>> q = QuestionFreeText(question_name="{{ bad_name }}", question_text="What is your name?")
185
180
  >>> s = Survey(questions=[q])
186
- >>> j = Jobs(survey = s)
187
- Traceback (most recent call last):
188
- ...
189
- ValueError: At least some question names are not valid: ['{{ bad_name }}']
190
-
181
+
191
182
  Notes:
192
183
  - The survey's questions must have valid names without templating variables
193
184
  - If agents, models, or scenarios are not provided, defaults will be used when running
@@ -206,10 +197,15 @@ class Jobs(Base):
206
197
 
207
198
  try:
208
199
  assert self.survey.question_names_valid()
209
- except Exception as e:
210
- invalid_question_names = [q.question_name for q in self.survey.questions if not q.is_valid_question_name()]
211
- raise ValueError(f"At least some question names are not valid: {invalid_question_names}")
212
-
200
+ except Exception:
201
+ invalid_question_names = [
202
+ q.question_name
203
+ for q in self.survey.questions
204
+ if not q.is_valid_question_name()
205
+ ]
206
+ raise JobsValueError(
207
+ f"At least some question names are not valid: {invalid_question_names}"
208
+ )
213
209
 
214
210
  def add_running_env(self, running_env: RunEnvironment):
215
211
  self.run_config.add_environment(running_env)
@@ -224,7 +220,7 @@ class Jobs(Base):
224
220
  self.run_config.add_cache(cache)
225
221
  return self
226
222
 
227
- def using_bucket_collection(self, bucket_collection: 'BucketCollection') -> Jobs:
223
+ def using_bucket_collection(self, bucket_collection: "BucketCollection") -> Jobs:
228
224
  """
229
225
  Add a BucketCollection to the job.
230
226
 
@@ -233,7 +229,7 @@ class Jobs(Base):
233
229
  self.run_config.add_bucket_collection(bucket_collection)
234
230
  return self
235
231
 
236
- def using_key_lookup(self, key_lookup: 'KeyLookup') -> Jobs:
232
+ def using_key_lookup(self, key_lookup: "KeyLookup") -> Jobs:
237
233
  """
238
234
  Add a KeyLookup to the job.
239
235
 
@@ -339,18 +335,18 @@ class Jobs(Base):
339
335
  ) -> "Jobs":
340
336
  """
341
337
  Add agents, scenarios, and language models to a job using a fluent interface.
342
-
338
+
343
339
  This method is the primary way to configure a Jobs instance with components.
344
340
  It intelligently handles different types of objects and collections, making
345
341
  it easy to build complex job configurations with a concise syntax.
346
-
342
+
347
343
  Parameters:
348
- *args: Objects or sequences of objects to add to the job.
344
+ *args: Objects or sequences of objects to add to the job.
349
345
  Supported types are Agent, Scenario, LanguageModel, and sequences of these.
350
-
346
+
351
347
  Returns:
352
348
  Jobs: The Jobs instance (self) for method chaining
353
-
349
+
354
350
  Examples:
355
351
  >>> from edsl.surveys import Survey
356
352
  >>> from edsl.questions import QuestionFreeText
@@ -361,17 +357,17 @@ class Jobs(Base):
361
357
  >>> from edsl.agents import Agent; a = Agent(traits = {"status": "Sad"})
362
358
  >>> j.by(a).agents
363
359
  AgentList([Agent(traits = {'status': 'Sad'})])
364
-
360
+
365
361
  # Adding multiple components at once
366
362
  >>> from edsl.language_models import Model
367
363
  >>> from edsl.scenarios import Scenario
368
364
  >>> j = Jobs.example()
369
365
  >>> _ = j.by(Agent(traits={"mood": "happy"})).by(Model(temperature=0.7)).by(Scenario({"time": "morning"}))
370
-
366
+
371
367
  # Adding a sequence of the same type
372
368
  >>> agents = [Agent(traits={"age": i}) for i in range(5)]
373
369
  >>> _ = j.by(agents)
374
-
370
+
375
371
  Notes:
376
372
  - All objects must implement 'get_value', 'set_value', and '__add__' methods
377
373
  - Agent traits: When adding agents with traits to existing agents, the traits are
@@ -475,16 +471,19 @@ class Jobs(Base):
475
471
 
476
472
  def show_flow(self, filename: Optional[str] = None) -> None:
477
473
  """Show the flow of the survey.
478
-
474
+
479
475
  >>> from edsl.jobs import Jobs
480
476
  >>> Jobs.example().show_flow()
481
477
  """
482
478
  from ..surveys import SurveyFlowVisualization
483
- if self.scenarios:
479
+
480
+ if self.scenarios:
484
481
  scenario = self.scenarios[0]
485
482
  else:
486
483
  scenario = None
487
- SurveyFlowVisualization(self.survey, scenario=scenario, agent=None).show_flow(filename=filename)
484
+ SurveyFlowVisualization(self.survey, scenario=scenario, agent=None).show_flow(
485
+ filename=filename
486
+ )
488
487
 
489
488
  def interviews(self) -> list[Interview]:
490
489
  """
@@ -585,7 +584,7 @@ class Jobs(Base):
585
584
  return user_edsl_settings.get("remote_caching", False)
586
585
  except requests.ConnectionError:
587
586
  pass
588
- except CoopServerResponseError as e:
587
+ except CoopServerResponseError:
589
588
  pass
590
589
 
591
590
  return False
@@ -604,7 +603,7 @@ class Jobs(Base):
604
603
  )
605
604
  return job_info
606
605
 
607
- def _create_remote_inference_handler(self) -> 'JobsRemoteInferenceHandler':
606
+ def _create_remote_inference_handler(self) -> "JobsRemoteInferenceHandler":
608
607
  return JobsRemoteInferenceHandler(
609
608
  self, verbose=self.run_config.parameters.verbose
610
609
  )
@@ -624,12 +623,12 @@ class Jobs(Base):
624
623
  from edsl.results import Results
625
624
 
626
625
  results = Results.from_job_info(job_info)
627
- return results
626
+ return results, None
628
627
  else:
629
- results = jh.poll_remote_inference_job(job_info)
630
- return results
628
+ results, reason = jh.poll_remote_inference_job(job_info)
629
+ return results, reason
631
630
  else:
632
- return None
631
+ return None, None
633
632
 
634
633
  def _prepare_to_run(self) -> None:
635
634
  "This makes sure that the job is ready to run and that keys are in place for a remote job."
@@ -646,9 +645,9 @@ class Jobs(Base):
646
645
  jc.check_api_keys()
647
646
 
648
647
  async def _execute_with_remote_cache(self, run_job_async: bool) -> Results:
649
- use_remote_cache = self.use_remote_cache()
648
+ # Remote cache usage determination happens inside this method
649
+ # use_remote_cache = self.use_remote_cache()
650
650
 
651
- from ..coop import Coop
652
651
  from .jobs_runner_asyncio import JobsRunnerAsyncio
653
652
  from ..caching import Cache
654
653
 
@@ -707,8 +706,9 @@ class Jobs(Base):
707
706
  self.run_config.environment.cache = Cache(immediate_write=False)
708
707
 
709
708
  # first try to run the job remotely
710
- if (results := self._remote_results(config)) is not None:
711
- return results
709
+ results, reason = self._remote_results(config)
710
+ if results is not None:
711
+ return results, reason
712
712
 
713
713
  self._check_if_local_keys_ok()
714
714
 
@@ -725,17 +725,17 @@ class Jobs(Base):
725
725
  self.run_config.environment.key_lookup
726
726
  )
727
727
 
728
- return None
728
+ return None, reason
729
729
 
730
730
  @with_config
731
731
  def run(self, *, config: RunConfig) -> "Results":
732
732
  """
733
733
  Runs the job by conducting interviews and returns their results.
734
-
734
+
735
735
  This is the main entry point for executing a job. It processes all interviews
736
736
  (combinations of agents, scenarios, and models) and returns a Results object
737
737
  containing all responses and metadata.
738
-
738
+
739
739
  Parameters:
740
740
  n (int): Number of iterations to run each interview (default: 1)
741
741
  progress_bar (bool): Whether to show a progress bar (default: False)
@@ -756,16 +756,16 @@ class Jobs(Base):
756
756
  cache (Cache, optional): Cache object to store results
757
757
  bucket_collection (BucketCollection, optional): Object to track API calls
758
758
  key_lookup (KeyLookup, optional): Object to manage API keys
759
-
759
+
760
760
  Returns:
761
761
  Results: A Results object containing all responses and metadata
762
-
762
+
763
763
  Notes:
764
764
  - This method will first try to use remote inference if available
765
765
  - If remote inference is not available, it will run locally
766
766
  - For long-running jobs, consider using progress_bar=True
767
767
  - For maximum performance, ensure appropriate caching is configured
768
-
768
+
769
769
  Example:
770
770
  >>> from edsl.jobs import Jobs
771
771
  >>> from edsl.caching import Cache
@@ -775,22 +775,25 @@ class Jobs(Base):
775
775
  >>> results = job.by(m).run(cache=Cache(), progress_bar=False, n=2, disable_remote_inference=True)
776
776
  ...
777
777
  """
778
- potentially_completed_results = self._run(config)
778
+ potentially_completed_results, reason = self._run(config)
779
779
 
780
780
  if potentially_completed_results is not None:
781
781
  return potentially_completed_results
782
782
 
783
+ if reason == "insufficient funds":
784
+ return None
785
+
783
786
  return asyncio.run(self._execute_with_remote_cache(run_job_async=False))
784
787
 
785
788
  @with_config
786
789
  async def run_async(self, *, config: RunConfig) -> "Results":
787
790
  """
788
791
  Asynchronously runs the job by conducting interviews and returns their results.
789
-
792
+
790
793
  This method is the asynchronous version of `run()`. It has the same functionality and
791
794
  parameters but can be awaited in an async context for better integration with
792
795
  asynchronous code.
793
-
796
+
794
797
  Parameters:
795
798
  n (int): Number of iterations to run each interview (default: 1)
796
799
  progress_bar (bool): Whether to show a progress bar (default: False)
@@ -811,15 +814,15 @@ class Jobs(Base):
811
814
  cache (Cache, optional): Cache object to store results
812
815
  bucket_collection (BucketCollection, optional): Object to track API calls
813
816
  key_lookup (KeyLookup, optional): Object to manage API keys
814
-
817
+
815
818
  Returns:
816
819
  Results: A Results object containing all responses and metadata
817
-
820
+
818
821
  Notes:
819
822
  - This method should be used in async contexts (e.g., with `await`)
820
823
  - For non-async contexts, use the `run()` method instead
821
824
  - This method is particularly useful in notebook environments or async applications
822
-
825
+
823
826
  Example:
824
827
  >>> import asyncio
825
828
  >>> from edsl.jobs import Jobs
@@ -960,7 +963,9 @@ class Jobs(Base):
960
963
  """Return the answer to a question. This is a method that can be added to an agent."""
961
964
 
962
965
  if random.random() < throw_exception_probability:
963
- raise Exception("Error!")
966
+ from .exceptions import JobsErrors
967
+
968
+ raise JobsErrors("Simulated error during question answering")
964
969
  return agent_answers[
965
970
  (self.traits["status"], question.question_name, scenario["period"])
966
971
  ]
@@ -1001,7 +1006,7 @@ class Jobs(Base):
1001
1006
 
1002
1007
  def code(self):
1003
1008
  """Return the code to create this instance."""
1004
- raise NotImplementedError
1009
+ raise JobsImplementationError("Code generation not implemented yet")
1005
1010
 
1006
1011
 
1007
1012
  def main():
edsl/jobs/jobs_checks.py CHANGED
@@ -3,7 +3,7 @@ Checks a Jobs object for missing API keys and other requirements.
3
3
  """
4
4
 
5
5
  import os
6
- from edsl.exceptions.general import MissingAPIKeyError
6
+ from edsl.key_management.key_lookup_builder import MissingAPIKeyError
7
7
 
8
8
 
9
9
  class JobsChecks:
@@ -28,6 +28,7 @@ class JobsChecks:
28
28
  raise MissingAPIKeyError(
29
29
  model_name=str(model.model),
30
30
  inference_service=model._inference_service_,
31
+ silent=False
31
32
  )
32
33
 
33
34
  def get_missing_api_keys(self) -> set:
@@ -36,7 +37,6 @@ class JobsChecks:
36
37
  """
37
38
  missing_api_keys = set()
38
39
 
39
- from edsl.language_models.model import Model
40
40
  from edsl.enums import service_to_api_keyname
41
41
 
42
42
  for model in self.jobs.models: # + [Model()]:
@@ -134,7 +134,6 @@ class JobsChecks:
134
134
  def key_process(self):
135
135
  import secrets
136
136
  from dotenv import load_dotenv
137
- from edsl.config import CONFIG
138
137
  from edsl.coop.coop import Coop
139
138
  from edsl.utilities.utilities import write_api_key_to_env
140
139
 
@@ -1,11 +1,11 @@
1
1
  from typing import Union, Sequence, TYPE_CHECKING
2
+ from .exceptions import JobsValueError
2
3
 
3
4
  if TYPE_CHECKING:
4
5
  from ..agents import Agent
5
6
  from ..language_models import LanguageModel
6
7
  from ..scenarios import Scenario
7
8
  from .jobs import Jobs
8
- from ..invigilators import InvigilatorBase
9
9
 
10
10
  class JobsComponentConstructor:
11
11
  "Handles the creation of Agents, Scenarios, and LanguageModels in a job."
@@ -132,7 +132,7 @@ class JobsComponentConstructor:
132
132
  key = class_to_key[class_type]
133
133
  break
134
134
  else:
135
- raise ValueError(
135
+ raise JobsValueError(
136
136
  f"First argument must be an Agent, Scenario, or LanguageModel, not {object}"
137
137
  )
138
138
  current_objects = getattr(self.jobs, key, None)
@@ -8,7 +8,8 @@ if TYPE_CHECKING:
8
8
  from ..agents import AgentList
9
9
  from ..scenarios import ScenarioList
10
10
  from ..surveys import Survey
11
- from .interviews.Interview import Interview
11
+ from ..interviews import Interview
12
+ from ..invigilators.invigilator_base import Invigilator
12
13
 
13
14
  from .fetch_invigilator import FetchInvigilator
14
15
  from ..caching import CacheEntry
@@ -142,7 +143,7 @@ class JobsPrompts:
142
143
  self._price_lookup = c.fetch_prices()
143
144
  return self._price_lookup
144
145
 
145
- def _process_one_invigilator(self, invigilator: 'Invigilator', interview_index: int, iterations: int = 1) -> dict :
146
+ def _process_one_invigilator(self, invigilator: 'Invigilator', interview_index: int, iterations: int = 1) -> dict:
146
147
  """Process a single invigilator and return a dictionary with all needed data fields."""
147
148
  prompts = invigilator.get_prompts()
148
149
  user_prompt = prompts["user_prompt"]
@@ -2,16 +2,17 @@ import re
2
2
  import sys
3
3
  import uuid
4
4
  from abc import ABC, abstractmethod
5
- from typing import Optional, Union, Literal, TYPE_CHECKING, List, Dict
5
+ from typing import Literal, TYPE_CHECKING, List
6
6
  from datetime import datetime
7
7
  from dataclasses import dataclass
8
8
 
9
- from ..coop import CoopServerResponseError
9
+ from .exceptions import JobsValueError
10
+
10
11
 
11
12
  from .jobs_status_enums import JobsStatus
12
13
 
13
14
  if TYPE_CHECKING:
14
- from ..results import Results
15
+ pass
15
16
 
16
17
 
17
18
  @dataclass
@@ -63,7 +64,7 @@ class JobLogger(ABC):
63
64
  '1234'
64
65
  """
65
66
  if information_type not in self.jobs_info.__annotations__:
66
- raise ValueError(f"Information type {information_type} not supported")
67
+ raise JobsValueError(f"Information type {information_type} not supported")
67
68
  setattr(self.jobs_info, information_type, value)
68
69
 
69
70
  @abstractmethod
@@ -21,7 +21,7 @@ import time
21
21
  import asyncio
22
22
  import threading
23
23
  import warnings
24
- from typing import TYPE_CHECKING, List, Generator, Tuple, Optional, Any
24
+ from typing import TYPE_CHECKING, Optional
25
25
 
26
26
  if TYPE_CHECKING:
27
27
  from ..results import Results
@@ -36,7 +36,6 @@ from .data_structures import RunEnvironment, RunParameters, RunConfig
36
36
 
37
37
  if TYPE_CHECKING:
38
38
  from ..jobs import Jobs
39
- from ..interviews import Interview
40
39
 
41
40
 
42
41
  class JobsRunnerAsyncio:
@@ -6,9 +6,12 @@ import requests
6
6
  from abc import ABC, abstractmethod
7
7
  from dataclasses import dataclass
8
8
  from collections import defaultdict
9
- from typing import Any, Dict, Optional
9
+ from typing import Any, Dict, Optional, TYPE_CHECKING
10
10
  from uuid import UUID
11
11
 
12
+ if TYPE_CHECKING:
13
+ from .jobs_runner_asyncio import JobsRunnerAsyncio
14
+
12
15
 
13
16
  @dataclass
14
17
  class ModelInfo:
@@ -171,16 +174,12 @@ class JobsRunnerStatusBase(ABC):
171
174
  status_dict["language_model_queues"] = model_queues
172
175
  return status_dict
173
176
 
174
- def add_completed_interview(self, result):
177
+ def add_completed_interview(self, interview):
175
178
  """Records a completed interview without storing the full interview data."""
176
179
  self.stats_tracker.add_completed_interview(
177
- model=result.model.model,
178
- num_exceptions=(
179
- len(result.exceptions) if hasattr(result, "exceptions") else 0
180
- ),
181
- num_unfixed=(
182
- result.exceptions.num_unfixed() if hasattr(result, "exceptions") else 0
183
- ),
180
+ model=interview.model.model,
181
+ num_exceptions=interview.exceptions.num_exceptions(),
182
+ num_unfixed=interview.exceptions.num_unfixed_exceptions(),
184
183
  )
185
184
 
186
185
  def _compute_statistic(self, stat_name: str):