edsl 0.1.31.dev4__py3-none-any.whl → 0.1.33__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 (188) hide show
  1. edsl/Base.py +9 -3
  2. edsl/TemplateLoader.py +24 -0
  3. edsl/__init__.py +8 -3
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +40 -8
  6. edsl/agents/AgentList.py +43 -0
  7. edsl/agents/Invigilator.py +136 -221
  8. edsl/agents/InvigilatorBase.py +148 -59
  9. edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +154 -85
  10. edsl/agents/__init__.py +1 -0
  11. edsl/auto/AutoStudy.py +117 -0
  12. edsl/auto/StageBase.py +230 -0
  13. edsl/auto/StageGenerateSurvey.py +178 -0
  14. edsl/auto/StageLabelQuestions.py +125 -0
  15. edsl/auto/StagePersona.py +61 -0
  16. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  17. edsl/auto/StagePersonaDimensionValues.py +74 -0
  18. edsl/auto/StagePersonaDimensions.py +69 -0
  19. edsl/auto/StageQuestions.py +73 -0
  20. edsl/auto/SurveyCreatorPipeline.py +21 -0
  21. edsl/auto/utilities.py +224 -0
  22. edsl/config.py +48 -47
  23. edsl/conjure/Conjure.py +6 -0
  24. edsl/coop/PriceFetcher.py +58 -0
  25. edsl/coop/coop.py +50 -7
  26. edsl/data/Cache.py +35 -1
  27. edsl/data/CacheHandler.py +3 -4
  28. edsl/data_transfer_models.py +73 -38
  29. edsl/enums.py +8 -0
  30. edsl/exceptions/general.py +10 -8
  31. edsl/exceptions/language_models.py +25 -1
  32. edsl/exceptions/questions.py +62 -5
  33. edsl/exceptions/results.py +4 -0
  34. edsl/inference_services/AnthropicService.py +13 -11
  35. edsl/inference_services/AwsBedrock.py +112 -0
  36. edsl/inference_services/AzureAI.py +214 -0
  37. edsl/inference_services/DeepInfraService.py +4 -3
  38. edsl/inference_services/GoogleService.py +16 -12
  39. edsl/inference_services/GroqService.py +5 -4
  40. edsl/inference_services/InferenceServiceABC.py +58 -3
  41. edsl/inference_services/InferenceServicesCollection.py +13 -8
  42. edsl/inference_services/MistralAIService.py +120 -0
  43. edsl/inference_services/OllamaService.py +18 -0
  44. edsl/inference_services/OpenAIService.py +55 -56
  45. edsl/inference_services/TestService.py +80 -0
  46. edsl/inference_services/TogetherAIService.py +170 -0
  47. edsl/inference_services/models_available_cache.py +25 -0
  48. edsl/inference_services/registry.py +19 -1
  49. edsl/jobs/Answers.py +10 -12
  50. edsl/jobs/FailedQuestion.py +78 -0
  51. edsl/jobs/Jobs.py +137 -41
  52. edsl/jobs/buckets/BucketCollection.py +24 -15
  53. edsl/jobs/buckets/TokenBucket.py +105 -18
  54. edsl/jobs/interviews/Interview.py +393 -83
  55. edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +22 -18
  56. edsl/jobs/interviews/InterviewExceptionEntry.py +167 -0
  57. edsl/jobs/runners/JobsRunnerAsyncio.py +152 -160
  58. edsl/jobs/runners/JobsRunnerStatus.py +331 -0
  59. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  60. edsl/jobs/tasks/TaskCreators.py +1 -1
  61. edsl/jobs/tasks/TaskHistory.py +205 -126
  62. edsl/language_models/LanguageModel.py +297 -177
  63. edsl/language_models/ModelList.py +2 -2
  64. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  65. edsl/language_models/fake_openai_call.py +15 -0
  66. edsl/language_models/fake_openai_service.py +61 -0
  67. edsl/language_models/registry.py +25 -8
  68. edsl/language_models/repair.py +0 -19
  69. edsl/language_models/utilities.py +61 -0
  70. edsl/notebooks/Notebook.py +20 -2
  71. edsl/prompts/Prompt.py +52 -2
  72. edsl/questions/AnswerValidatorMixin.py +23 -26
  73. edsl/questions/QuestionBase.py +330 -249
  74. edsl/questions/QuestionBaseGenMixin.py +133 -0
  75. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  76. edsl/questions/QuestionBudget.py +99 -42
  77. edsl/questions/QuestionCheckBox.py +227 -36
  78. edsl/questions/QuestionExtract.py +98 -28
  79. edsl/questions/QuestionFreeText.py +47 -31
  80. edsl/questions/QuestionFunctional.py +7 -0
  81. edsl/questions/QuestionList.py +141 -23
  82. edsl/questions/QuestionMultipleChoice.py +159 -66
  83. edsl/questions/QuestionNumerical.py +88 -47
  84. edsl/questions/QuestionRank.py +182 -25
  85. edsl/questions/Quick.py +41 -0
  86. edsl/questions/RegisterQuestionsMeta.py +31 -12
  87. edsl/questions/ResponseValidatorABC.py +170 -0
  88. edsl/questions/__init__.py +3 -4
  89. edsl/questions/decorators.py +21 -0
  90. edsl/questions/derived/QuestionLikertFive.py +10 -5
  91. edsl/questions/derived/QuestionLinearScale.py +15 -2
  92. edsl/questions/derived/QuestionTopK.py +10 -1
  93. edsl/questions/derived/QuestionYesNo.py +24 -3
  94. edsl/questions/descriptors.py +43 -7
  95. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  96. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  97. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  98. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  99. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  100. edsl/questions/prompt_templates/question_list.jinja +17 -0
  101. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  102. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  103. edsl/questions/question_registry.py +6 -2
  104. edsl/questions/templates/__init__.py +0 -0
  105. edsl/questions/templates/budget/__init__.py +0 -0
  106. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  107. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  108. edsl/questions/templates/checkbox/__init__.py +0 -0
  109. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  110. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  111. edsl/questions/templates/extract/__init__.py +0 -0
  112. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  113. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  114. edsl/questions/templates/free_text/__init__.py +0 -0
  115. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  116. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  117. edsl/questions/templates/likert_five/__init__.py +0 -0
  118. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  119. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  120. edsl/questions/templates/linear_scale/__init__.py +0 -0
  121. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  122. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  123. edsl/questions/templates/list/__init__.py +0 -0
  124. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  125. edsl/questions/templates/list/question_presentation.jinja +5 -0
  126. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  127. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  128. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  129. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  130. edsl/questions/templates/numerical/__init__.py +0 -0
  131. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  132. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  133. edsl/questions/templates/rank/__init__.py +0 -0
  134. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  135. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  136. edsl/questions/templates/top_k/__init__.py +0 -0
  137. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  138. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  139. edsl/questions/templates/yes_no/__init__.py +0 -0
  140. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  141. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  142. edsl/results/Dataset.py +20 -0
  143. edsl/results/DatasetExportMixin.py +58 -30
  144. edsl/results/DatasetTree.py +145 -0
  145. edsl/results/Result.py +32 -5
  146. edsl/results/Results.py +135 -46
  147. edsl/results/ResultsDBMixin.py +3 -3
  148. edsl/results/Selector.py +118 -0
  149. edsl/results/tree_explore.py +115 -0
  150. edsl/scenarios/FileStore.py +71 -10
  151. edsl/scenarios/Scenario.py +109 -24
  152. edsl/scenarios/ScenarioImageMixin.py +2 -2
  153. edsl/scenarios/ScenarioList.py +546 -21
  154. edsl/scenarios/ScenarioListExportMixin.py +24 -4
  155. edsl/scenarios/ScenarioListPdfMixin.py +153 -4
  156. edsl/study/SnapShot.py +8 -1
  157. edsl/study/Study.py +32 -0
  158. edsl/surveys/Rule.py +15 -3
  159. edsl/surveys/RuleCollection.py +21 -5
  160. edsl/surveys/Survey.py +707 -298
  161. edsl/surveys/SurveyExportMixin.py +71 -9
  162. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  163. edsl/surveys/SurveyQualtricsImport.py +284 -0
  164. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  165. edsl/surveys/instructions/Instruction.py +34 -0
  166. edsl/surveys/instructions/InstructionCollection.py +77 -0
  167. edsl/surveys/instructions/__init__.py +0 -0
  168. edsl/templates/error_reporting/base.html +24 -0
  169. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  170. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  171. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  172. edsl/templates/error_reporting/interview_details.html +116 -0
  173. edsl/templates/error_reporting/interviews.html +10 -0
  174. edsl/templates/error_reporting/overview.html +5 -0
  175. edsl/templates/error_reporting/performance_plot.html +2 -0
  176. edsl/templates/error_reporting/report.css +74 -0
  177. edsl/templates/error_reporting/report.html +118 -0
  178. edsl/templates/error_reporting/report.js +25 -0
  179. edsl/utilities/utilities.py +40 -1
  180. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/METADATA +8 -2
  181. edsl-0.1.33.dist-info/RECORD +295 -0
  182. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -271
  183. edsl/jobs/interviews/retry_management.py +0 -37
  184. edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -303
  185. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  186. edsl-0.1.31.dev4.dist-info/RECORD +0 -204
  187. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
  188. {edsl-0.1.31.dev4.dist-info → edsl-0.1.33.dist-info}/WHEEL +0 -0
@@ -0,0 +1,78 @@
1
+ from edsl.questions import QuestionBase
2
+ from edsl import Question, Scenario, Model, Agent
3
+
4
+ from edsl.language_models.LanguageModel import LanguageModel
5
+
6
+
7
+ class FailedQuestion:
8
+ # tests/jobs/test_Interview.py::test_handle_model_exceptions
9
+
10
+ # (Pdb) dir(self.exception.__traceback__)
11
+ # ['tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next']
12
+
13
+ def __init__(
14
+ self, question, scenario, model, agent, raw_model_response, exception, prompts
15
+ ):
16
+ self.question = question
17
+ self.scenario = scenario
18
+ self.model = model
19
+ self.agent = agent
20
+ self.raw_model_response = raw_model_response # JSON
21
+ self.exception = exception
22
+ self.prompts = prompts
23
+
24
+ def to_dict(self):
25
+ return {
26
+ "question": self.question._to_dict(),
27
+ "scenario": self.scenario._to_dict(),
28
+ "model": self.model._to_dict(),
29
+ "agent": self.agent._to_dict(),
30
+ "raw_model_response": self.raw_model_response,
31
+ "exception": self.exception.__class__.__name__, # self.exception,
32
+ "prompts": self.prompts,
33
+ }
34
+
35
+ @classmethod
36
+ def from_dict(cls, data):
37
+ question = QuestionBase.from_dict(data["question"])
38
+ scenario = Scenario.from_dict(data["scenario"])
39
+ model = LanguageModel.from_dict(data["model"])
40
+ agent = Agent.from_dict(data["agent"])
41
+ raw_model_response = data["raw_model_response"]
42
+ exception = data["exception"]
43
+ prompts = data["prompts"]
44
+ return cls(
45
+ question, scenario, model, agent, raw_model_response, exception, prompts
46
+ )
47
+
48
+ def __repr__(self):
49
+ return f"{self.__class__.__name__}(question={repr(self.question)}, scenario={repr(self.scenario)}, model={repr(self.model)}, agent={repr(self.agent)}, raw_model_response={repr(self.raw_model_response)}, exception={repr(self.exception)})"
50
+
51
+ @property
52
+ def jobs(self):
53
+ return self.question.by(self.scenario).by(self.agent).by(self.model)
54
+
55
+ def rerun(self):
56
+ results = self.jobs.run()
57
+ return results
58
+
59
+ def help(self):
60
+ pass
61
+
62
+ @classmethod
63
+ def example(cls):
64
+ from edsl.language_models.utilities import create_language_model
65
+ from edsl.language_models.utilities import create_survey
66
+
67
+ survey = create_survey(2, chained=False, take_scenario=False)
68
+ fail_at_number = 1
69
+ model = create_language_model(ValueError, fail_at_number)()
70
+ from edsl import Survey
71
+
72
+ results = survey.by(model).run()
73
+ return results.failed_questions[0][0]
74
+
75
+
76
+ if __name__ == "__main__":
77
+ fq = FailedQuestion.example()
78
+ new_fq = FailedQuestion.from_dict(fq.to_dict())
edsl/jobs/Jobs.py CHANGED
@@ -39,6 +39,8 @@ class Jobs(Base):
39
39
 
40
40
  self.__bucket_collection = None
41
41
 
42
+ # these setters and getters are used to ensure that the agents, models, and scenarios are stored as AgentList, ModelList, and ScenarioList objects
43
+
42
44
  @property
43
45
  def models(self):
44
46
  return self._models
@@ -119,7 +121,9 @@ class Jobs(Base):
119
121
  - scenarios: traits of new scenarios are combined with traits of old existing. New scenarios will overwrite overlapping traits, and do not increase the number of scenarios in the instance
120
122
  - models: new models overwrite old models.
121
123
  """
122
- passed_objects = self._turn_args_to_list(args)
124
+ passed_objects = self._turn_args_to_list(
125
+ args
126
+ ) # objects can also be passed comma-separated
123
127
 
124
128
  current_objects, objects_key = self._get_current_objects_of_this_type(
125
129
  passed_objects[0]
@@ -152,7 +156,11 @@ class Jobs(Base):
152
156
  from edsl.results.Dataset import Dataset
153
157
 
154
158
  for interview_index, interview in enumerate(interviews):
155
- invigilators = list(interview._build_invigilators(debug=False))
159
+ invigilators = [
160
+ interview._get_invigilator(question)
161
+ for question in self.survey.questions
162
+ ]
163
+ # list(interview._build_invigilators(debug=False))
156
164
  for _, invigilator in enumerate(invigilators):
157
165
  prompts = invigilator.get_prompts()
158
166
  user_prompts.append(prompts["user_prompt"])
@@ -176,17 +184,27 @@ class Jobs(Base):
176
184
  from edsl.agents.Agent import Agent
177
185
  from edsl.scenarios.Scenario import Scenario
178
186
  from edsl.scenarios.ScenarioList import ScenarioList
187
+ from edsl.language_models.ModelList import ModelList
179
188
 
180
189
  if isinstance(object, Agent):
181
190
  return AgentList
182
191
  elif isinstance(object, Scenario):
183
192
  return ScenarioList
193
+ elif isinstance(object, ModelList):
194
+ return ModelList
184
195
  else:
185
196
  return list
186
197
 
187
198
  @staticmethod
188
199
  def _turn_args_to_list(args):
189
- """Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments."""
200
+ """Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments.
201
+
202
+ Example:
203
+
204
+ >>> Jobs._turn_args_to_list([1,2,3])
205
+ [1, 2, 3]
206
+
207
+ """
190
208
 
191
209
  def did_user_pass_a_sequence(args):
192
210
  """Return True if the user passed a sequence, False otherwise.
@@ -209,7 +227,7 @@ class Jobs(Base):
209
227
  return container_class(args)
210
228
 
211
229
  def _get_current_objects_of_this_type(
212
- self, object: Union[Agent, Scenario, LanguageModel]
230
+ self, object: Union["Agent", "Scenario", "LanguageModel"]
213
231
  ) -> tuple[list, str]:
214
232
  from edsl.agents.Agent import Agent
215
233
  from edsl.scenarios.Scenario import Scenario
@@ -292,7 +310,11 @@ class Jobs(Base):
292
310
 
293
311
  @classmethod
294
312
  def from_interviews(cls, interview_list):
295
- """Return a Jobs instance from a list of interviews."""
313
+ """Return a Jobs instance from a list of interviews.
314
+
315
+ This is useful when you have, say, a list of failed interviews and you want to create
316
+ a new job with only those interviews.
317
+ """
296
318
  survey = interview_list[0].survey
297
319
  # get all the models
298
320
  models = list(set([interview.model for interview in interview_list]))
@@ -308,6 +330,8 @@ class Jobs(Base):
308
330
  Note that this sets the agents, model and scenarios if they have not been set. This is a side effect of the method.
309
331
  This is useful because a user can create a job without setting the agents, models, or scenarios, and the job will still run,
310
332
  with us filling in defaults.
333
+
334
+
311
335
  """
312
336
  # if no agents, models, or scenarios are set, set them to defaults
313
337
  from edsl.agents.Agent import Agent
@@ -319,7 +343,12 @@ class Jobs(Base):
319
343
  self.scenarios = self.scenarios or [Scenario()]
320
344
  for agent, scenario, model in product(self.agents, self.scenarios, self.models):
321
345
  yield Interview(
322
- survey=self.survey, agent=agent, scenario=scenario, model=model
346
+ survey=self.survey,
347
+ agent=agent,
348
+ scenario=scenario,
349
+ model=model,
350
+ skip_retry=self.skip_retry,
351
+ raise_validation_errors=self.raise_validation_errors,
323
352
  )
324
353
 
325
354
  def create_bucket_collection(self) -> BucketCollection:
@@ -359,10 +388,16 @@ class Jobs(Base):
359
388
  return links
360
389
 
361
390
  def __hash__(self):
362
- """Allow the model to be used as a key in a dictionary."""
391
+ """Allow the model to be used as a key in a dictionary.
392
+
393
+ >>> from edsl.jobs import Jobs
394
+ >>> hash(Jobs.example())
395
+ 846655441787442972
396
+
397
+ """
363
398
  from edsl.utilities.utilities import dict_hash
364
399
 
365
- return dict_hash(self.to_dict())
400
+ return dict_hash(self._to_dict())
366
401
 
367
402
  def _output(self, message) -> None:
368
403
  """Check if a Job is verbose. If so, print the message."""
@@ -390,11 +425,27 @@ class Jobs(Base):
390
425
  Traceback (most recent call last):
391
426
  ...
392
427
  ValueError: The following parameters are in the scenarios but not in the survey: {'plop'}
428
+
429
+ >>> q = QuestionFreeText(question_text = "Hello", question_name = "ugly_question")
430
+ >>> s = Scenario({'ugly_question': "B"})
431
+ >>> j = Jobs(survey = Survey(questions=[q])).by(s)
432
+ >>> j._check_parameters()
433
+ Traceback (most recent call last):
434
+ ...
435
+ ValueError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues.
393
436
  """
394
437
  survey_parameters: set = self.survey.parameters
395
438
  scenario_parameters: set = self.scenarios.parameters
396
439
 
397
- msg1, msg2 = None, None
440
+ msg0, msg1, msg2 = None, None, None
441
+
442
+ # look for key issues
443
+ if intersection := set(self.scenarios.parameters) & set(
444
+ self.survey.question_names
445
+ ):
446
+ msg0 = f"The following names are in both the survey question_names and the scenario keys: {intersection}. This will create issues."
447
+
448
+ raise ValueError(msg0)
398
449
 
399
450
  if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
400
451
  msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
@@ -409,26 +460,44 @@ class Jobs(Base):
409
460
  if warn:
410
461
  warnings.warn(message)
411
462
 
463
+ if self.scenarios.has_jinja_braces:
464
+ warnings.warn(
465
+ "The scenarios have Jinja braces ({{ and }}). Converting to '<<' and '>>'. If you want a different conversion, use the convert_jinja_braces method first to modify the scenario."
466
+ )
467
+ self.scenarios = self.scenarios.convert_jinja_braces()
468
+
469
+ @property
470
+ def skip_retry(self):
471
+ if not hasattr(self, "_skip_retry"):
472
+ return False
473
+ return self._skip_retry
474
+
475
+ @property
476
+ def raise_validation_errors(self):
477
+ if not hasattr(self, "_raise_validation_errors"):
478
+ return False
479
+ return self._raise_validation_errors
480
+
412
481
  def run(
413
482
  self,
414
483
  n: int = 1,
415
- debug: bool = False,
416
484
  progress_bar: bool = False,
417
485
  stop_on_exception: bool = False,
418
486
  cache: Union[Cache, bool] = None,
419
487
  check_api_keys: bool = False,
420
488
  sidecar_model: Optional[LanguageModel] = None,
421
- batch_mode: Optional[bool] = None,
422
489
  verbose: bool = False,
423
490
  print_exceptions=True,
424
491
  remote_cache_description: Optional[str] = None,
425
492
  remote_inference_description: Optional[str] = None,
493
+ skip_retry: bool = False,
494
+ raise_validation_errors: bool = False,
495
+ disable_remote_inference: bool = False,
426
496
  ) -> Results:
427
497
  """
428
498
  Runs the Job: conducts Interviews and returns their results.
429
499
 
430
500
  :param n: how many times to run each interview
431
- :param debug: prints debug messages
432
501
  :param progress_bar: shows a progress bar
433
502
  :param stop_on_exception: stops the job if an exception is raised
434
503
  :param cache: a cache object to store results
@@ -441,22 +510,22 @@ class Jobs(Base):
441
510
  from edsl.coop.coop import Coop
442
511
 
443
512
  self._check_parameters()
444
-
445
- if batch_mode is not None:
446
- raise NotImplementedError(
447
- "Batch mode is deprecated. Please update your code to not include 'batch_mode' in the 'run' method."
448
- )
513
+ self._skip_retry = skip_retry
514
+ self._raise_validation_errors = raise_validation_errors
449
515
 
450
516
  self.verbose = verbose
451
517
 
452
- try:
453
- coop = Coop()
454
- user_edsl_settings = coop.edsl_settings
455
- remote_cache = user_edsl_settings["remote_caching"]
456
- remote_inference = user_edsl_settings["remote_inference"]
457
- except Exception:
458
- remote_cache = False
459
- remote_inference = False
518
+ remote_cache = False
519
+ remote_inference = False
520
+
521
+ if not disable_remote_inference:
522
+ try:
523
+ coop = Coop()
524
+ user_edsl_settings = Coop().edsl_settings
525
+ remote_cache = user_edsl_settings.get("remote_caching", False)
526
+ remote_inference = user_edsl_settings.get("remote_inference", False)
527
+ except Exception:
528
+ pass
460
529
 
461
530
  if remote_inference:
462
531
  import time
@@ -508,7 +577,7 @@ class Jobs(Base):
508
577
  )
509
578
  return results
510
579
  else:
511
- duration = 10 if len(self) < 10 else 60
580
+ duration = 5
512
581
  time_checked = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
513
582
  frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
514
583
  start_time = time.time()
@@ -533,7 +602,7 @@ class Jobs(Base):
533
602
  )
534
603
 
535
604
  # handle cache
536
- if cache is None:
605
+ if cache is None or cache is True:
537
606
  from edsl.data.CacheHandler import CacheHandler
538
607
 
539
608
  cache = CacheHandler().get_cache()
@@ -545,12 +614,12 @@ class Jobs(Base):
545
614
  if not remote_cache:
546
615
  results = self._run_local(
547
616
  n=n,
548
- debug=debug,
549
617
  progress_bar=progress_bar,
550
618
  cache=cache,
551
619
  stop_on_exception=stop_on_exception,
552
620
  sidecar_model=sidecar_model,
553
621
  print_exceptions=print_exceptions,
622
+ raise_validation_errors=raise_validation_errors,
554
623
  )
555
624
 
556
625
  results.cache = cache.new_entries_cache()
@@ -589,12 +658,12 @@ class Jobs(Base):
589
658
  self._output("Running job...")
590
659
  results = self._run_local(
591
660
  n=n,
592
- debug=debug,
593
661
  progress_bar=progress_bar,
594
662
  cache=cache,
595
663
  stop_on_exception=stop_on_exception,
596
664
  sidecar_model=sidecar_model,
597
665
  print_exceptions=print_exceptions,
666
+ raise_validation_errors=raise_validation_errors,
598
667
  )
599
668
  self._output("Job completed!")
600
669
 
@@ -631,12 +700,16 @@ class Jobs(Base):
631
700
  return results
632
701
 
633
702
  async def run_async(self, cache=None, n=1, **kwargs):
634
- """Run the job asynchronously."""
703
+ """Run asynchronously."""
635
704
  results = await JobsRunnerAsyncio(self).run_async(cache=cache, n=n, **kwargs)
636
705
  return results
637
706
 
638
707
  def all_question_parameters(self):
639
- """Return all the fields in the questions in the survey."""
708
+ """Return all the fields in the questions in the survey.
709
+ >>> from edsl.jobs import Jobs
710
+ >>> Jobs.example().all_question_parameters()
711
+ {'period'}
712
+ """
640
713
  return set.union(*[question.parameters for question in self.survey.questions])
641
714
 
642
715
  #######################
@@ -677,15 +750,19 @@ class Jobs(Base):
677
750
  #######################
678
751
  # Serialization methods
679
752
  #######################
753
+
754
+ def _to_dict(self):
755
+ return {
756
+ "survey": self.survey._to_dict(),
757
+ "agents": [agent._to_dict() for agent in self.agents],
758
+ "models": [model._to_dict() for model in self.models],
759
+ "scenarios": [scenario._to_dict() for scenario in self.scenarios],
760
+ }
761
+
680
762
  @add_edsl_version
681
763
  def to_dict(self) -> dict:
682
764
  """Convert the Jobs instance to a dictionary."""
683
- return {
684
- "survey": self.survey.to_dict(),
685
- "agents": [agent.to_dict() for agent in self.agents],
686
- "models": [model.to_dict() for model in self.models],
687
- "scenarios": [scenario.to_dict() for scenario in self.scenarios],
688
- }
765
+ return self._to_dict()
689
766
 
690
767
  @classmethod
691
768
  @remove_edsl_version
@@ -704,7 +781,13 @@ class Jobs(Base):
704
781
  )
705
782
 
706
783
  def __eq__(self, other: Jobs) -> bool:
707
- """Return True if the Jobs instance is equal to another Jobs instance."""
784
+ """Return True if the Jobs instance is equal to another Jobs instance.
785
+
786
+ >>> from edsl.jobs import Jobs
787
+ >>> Jobs.example() == Jobs.example()
788
+ True
789
+
790
+ """
708
791
  return self.to_dict() == other.to_dict()
709
792
 
710
793
  #######################
@@ -712,11 +795,16 @@ class Jobs(Base):
712
795
  #######################
713
796
  @classmethod
714
797
  def example(
715
- cls, throw_exception_probability: int = 0, randomize: bool = False
798
+ cls,
799
+ throw_exception_probability: float = 0.0,
800
+ randomize: bool = False,
801
+ test_model=False,
716
802
  ) -> Jobs:
717
803
  """Return an example Jobs instance.
718
804
 
719
805
  :param throw_exception_probability: the probability that an exception will be thrown when answering a question. This is useful for testing error handling.
806
+ :param randomize: whether to randomize the job by adding a random string to the period
807
+ :param test_model: whether to use a test model
720
808
 
721
809
  >>> Jobs.example()
722
810
  Jobs(...)
@@ -730,6 +818,11 @@ class Jobs(Base):
730
818
 
731
819
  addition = "" if not randomize else str(uuid4())
732
820
 
821
+ if test_model:
822
+ from edsl.language_models import LanguageModel
823
+
824
+ m = LanguageModel.example(test_model=True)
825
+
733
826
  # (status, question, period)
734
827
  agent_answers = {
735
828
  ("Joyful", "how_feeling", "morning"): "OK",
@@ -777,7 +870,10 @@ class Jobs(Base):
777
870
  Scenario({"period": "afternoon"}),
778
871
  ]
779
872
  )
780
- job = base_survey.by(scenario_list).by(joy_agent, sad_agent)
873
+ if test_model:
874
+ job = base_survey.by(m).by(scenario_list).by(joy_agent, sad_agent)
875
+ else:
876
+ job = base_survey.by(scenario_list).by(joy_agent, sad_agent)
781
877
 
782
878
  return job
783
879
 
@@ -802,7 +898,7 @@ def main():
802
898
 
803
899
  job = Jobs.example()
804
900
  len(job) == 8
805
- results = job.run(debug=True, cache=Cache())
901
+ results = job.run(cache=Cache())
806
902
  len(results) == 8
807
903
  results
808
904
 
@@ -13,6 +13,8 @@ class BucketCollection(UserDict):
13
13
  def __init__(self, infinity_buckets=False):
14
14
  super().__init__()
15
15
  self.infinity_buckets = infinity_buckets
16
+ self.models_to_services = {}
17
+ self.services_to_buckets = {}
16
18
 
17
19
  def __repr__(self):
18
20
  return f"BucketCollection({self.data})"
@@ -21,6 +23,7 @@ class BucketCollection(UserDict):
21
23
  """Adds a model to the bucket collection.
22
24
 
23
25
  This will create the token and request buckets for the model."""
26
+
24
27
  # compute the TPS and RPS from the model
25
28
  if not self.infinity_buckets:
26
29
  TPS = model.TPM / 60.0
@@ -29,22 +32,28 @@ class BucketCollection(UserDict):
29
32
  TPS = float("inf")
30
33
  RPS = float("inf")
31
34
 
32
- # create the buckets
33
- requests_bucket = TokenBucket(
34
- bucket_name=model.model,
35
- bucket_type="requests",
36
- capacity=RPS,
37
- refill_rate=RPS,
38
- )
39
- tokens_bucket = TokenBucket(
40
- bucket_name=model.model, bucket_type="tokens", capacity=TPS, refill_rate=TPS
41
- )
42
- model_buckets = ModelBuckets(requests_bucket, tokens_bucket)
43
- if model in self:
44
- # it if already exists, combine the buckets
45
- self[model] += model_buckets
35
+ if model.model not in self.models_to_services:
36
+ service = model._inference_service_
37
+ if service not in self.services_to_buckets:
38
+ requests_bucket = TokenBucket(
39
+ bucket_name=service,
40
+ bucket_type="requests",
41
+ capacity=RPS,
42
+ refill_rate=RPS,
43
+ )
44
+ tokens_bucket = TokenBucket(
45
+ bucket_name=service,
46
+ bucket_type="tokens",
47
+ capacity=TPS,
48
+ refill_rate=TPS,
49
+ )
50
+ self.services_to_buckets[service] = ModelBuckets(
51
+ requests_bucket, tokens_bucket
52
+ )
53
+ self.models_to_services[model.model] = service
54
+ self[model] = self.services_to_buckets[service]
46
55
  else:
47
- self[model] = model_buckets
56
+ self[model] = self.services_to_buckets[self.models_to_services[model.model]]
48
57
 
49
58
  def visualize(self) -> dict:
50
59
  """Visualize the token and request buckets for each model."""