edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev2__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 (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
edsl/jobs/Jobs.py CHANGED
@@ -1,17 +1,14 @@
1
1
  # """The Jobs class is a collection of agents, scenarios and models and one survey."""
2
2
  from __future__ import annotations
3
3
  import warnings
4
- import requests
5
- from itertools import product
6
4
  from typing import Literal, Optional, Union, Sequence, Generator, TYPE_CHECKING
7
5
 
8
6
  from edsl.Base import Base
9
7
 
10
- from edsl.exceptions import MissingAPIKeyError
11
8
  from edsl.jobs.buckets.BucketCollection import BucketCollection
9
+ from edsl.jobs.JobsPrompts import JobsPrompts
12
10
  from edsl.jobs.interviews.Interview import Interview
13
- from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
14
- from edsl.utilities.decorators import remove_edsl_version
11
+ from edsl.utilities.remove_edsl_version import remove_edsl_version
15
12
 
16
13
  from edsl.data.RemoteCacheSync import RemoteCacheSync
17
14
  from edsl.exceptions.coop import CoopServerResponseError
@@ -21,16 +18,16 @@ if TYPE_CHECKING:
21
18
  from edsl.agents.AgentList import AgentList
22
19
  from edsl.language_models.LanguageModel import LanguageModel
23
20
  from edsl.scenarios.Scenario import Scenario
21
+ from edsl.scenarios.ScenarioList import ScenarioList
24
22
  from edsl.surveys.Survey import Survey
25
23
  from edsl.results.Results import Results
26
24
  from edsl.results.Dataset import Dataset
25
+ from edsl.language_models.ModelList import ModelList
27
26
 
28
27
 
29
28
  class Jobs(Base):
30
29
  """
31
- A collection of agents, scenarios and models and one survey.
32
- The actual running of a job is done by a `JobsRunner`, which is a subclass of `JobsRunner`.
33
- The `JobsRunner` is chosen by the user, and is stored in the `jobs_runner_name` attribute.
30
+ A collection of agents, scenarios and models and one survey that creates 'interviews'
34
31
  """
35
32
 
36
33
  __documentation__ = "https://docs.expectedparrot.com/en/latest/jobs.html"
@@ -38,9 +35,9 @@ class Jobs(Base):
38
35
  def __init__(
39
36
  self,
40
37
  survey: "Survey",
41
- agents: Optional[list["Agent"]] = None,
42
- models: Optional[list["LanguageModel"]] = None,
43
- scenarios: Optional[list["Scenario"]] = None,
38
+ agents: Optional[Union[list[Agent], AgentList]] = None,
39
+ models: Optional[Union[ModelList, list[LanguageModel]]] = None,
40
+ scenarios: Optional[Union[ScenarioList, list[Scenario]]] = None,
44
41
  ):
45
42
  """Initialize a Jobs instance.
46
43
 
@@ -50,13 +47,14 @@ class Jobs(Base):
50
47
  :param scenarios: a list of scenarios
51
48
  """
52
49
  self.survey = survey
53
- self.agents: "AgentList" = agents
54
- self.scenarios: "ScenarioList" = scenarios
50
+ self.agents: AgentList = agents
51
+ self.scenarios: ScenarioList = scenarios
55
52
  self.models = models
56
53
 
57
54
  self.__bucket_collection = None
58
55
 
59
- # these setters and getters are used to ensure that the agents, models, and scenarios are stored as AgentList, ModelList, and ScenarioList objects
56
+ # these setters and getters are used to ensure that the agents, models, and scenarios
57
+ # are stored as AgentList, ModelList, and ScenarioList objects.
60
58
 
61
59
  @property
62
60
  def models(self):
@@ -64,7 +62,7 @@ class Jobs(Base):
64
62
 
65
63
  @models.setter
66
64
  def models(self, value):
67
- from edsl import ModelList
65
+ from edsl.language_models.ModelList import ModelList
68
66
 
69
67
  if value:
70
68
  if not isinstance(value, ModelList):
@@ -80,7 +78,7 @@ class Jobs(Base):
80
78
 
81
79
  @agents.setter
82
80
  def agents(self, value):
83
- from edsl import AgentList
81
+ from edsl.agents.AgentList import AgentList
84
82
 
85
83
  if value:
86
84
  if not isinstance(value, AgentList):
@@ -96,7 +94,7 @@ class Jobs(Base):
96
94
 
97
95
  @scenarios.setter
98
96
  def scenarios(self, value):
99
- from edsl import ScenarioList
97
+ from edsl.scenarios.ScenarioList import ScenarioList
100
98
  from edsl.results.Dataset import Dataset
101
99
 
102
100
  if value:
@@ -115,28 +113,32 @@ class Jobs(Base):
115
113
  def by(
116
114
  self,
117
115
  *args: Union[
118
- "Agent",
119
- "Scenario",
120
- "LanguageModel",
116
+ Agent,
117
+ Scenario,
118
+ LanguageModel,
121
119
  Sequence[Union["Agent", "Scenario", "LanguageModel"]],
122
120
  ],
123
121
  ) -> Jobs:
124
122
  """
125
- Add Agents, Scenarios and LanguageModels to a job. If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute. Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
123
+ Add Agents, Scenarios and LanguageModels to a job.
124
+
125
+ :param args: objects or a sequence (list, tuple, ...) of objects of the same type
126
+
127
+ If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
128
+ Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
126
129
 
127
130
  This 'by' is intended to create a fluent interface.
128
131
 
129
- >>> from edsl import Survey
130
- >>> from edsl import QuestionFreeText
132
+ >>> from edsl.surveys.Survey import Survey
133
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
131
134
  >>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
132
135
  >>> j = Jobs(survey = Survey(questions=[q]))
133
136
  >>> j
134
137
  Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
135
- >>> from edsl import Agent; a = Agent(traits = {"status": "Sad"})
138
+ >>> from edsl.agents.Agent import Agent; a = Agent(traits = {"status": "Sad"})
136
139
  >>> j.by(a).agents
137
140
  AgentList([Agent(traits = {'status': 'Sad'})])
138
141
 
139
- :param args: objects or a sequence (list, tuple, ...) of objects of the same type
140
142
 
141
143
  Notes:
142
144
  - all objects must implement the 'get_value', 'set_value', and `__add__` methods
@@ -144,28 +146,9 @@ class Jobs(Base):
144
146
  - 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
145
147
  - models: new models overwrite old models.
146
148
  """
147
- from edsl.results.Dataset import Dataset
149
+ from edsl.jobs.JobsComponentConstructor import JobsComponentConstructor
148
150
 
149
- if isinstance(
150
- args[0], Dataset
151
- ): # let the user user a Dataset as if it were a ScenarioList
152
- args = args[0].to_scenario_list()
153
-
154
- passed_objects = self._turn_args_to_list(
155
- args
156
- ) # objects can also be passed comma-separated
157
-
158
- current_objects, objects_key = self._get_current_objects_of_this_type(
159
- passed_objects[0]
160
- )
161
-
162
- if not current_objects:
163
- new_objects = passed_objects
164
- else:
165
- new_objects = self._merge_objects(passed_objects, current_objects)
166
-
167
- setattr(self, objects_key, new_objects) # update the job
168
- return self
151
+ return JobsComponentConstructor(self).by(*args)
169
152
 
170
153
  def prompts(self) -> "Dataset":
171
154
  """Return a Dataset of prompts that will be used.
@@ -175,12 +158,9 @@ class Jobs(Base):
175
158
  >>> Jobs.example().prompts()
176
159
  Dataset(...)
177
160
  """
178
- from edsl.jobs.JobsPrompts import JobsPrompts
179
-
180
- j = JobsPrompts(self)
181
- return j.prompts()
161
+ return JobsPrompts(self).prompts()
182
162
 
183
- def show_prompts(self, all=False) -> None:
163
+ def show_prompts(self, all: bool = False) -> None:
184
164
  """Print the prompts."""
185
165
  if all:
186
166
  return self.prompts().to_scenario_list().table()
@@ -200,9 +180,12 @@ class Jobs(Base):
200
180
  """
201
181
  Estimate the cost of running the prompts.
202
182
  :param iterations: the number of iterations to run
183
+ :param system_prompt: the system prompt
184
+ :param user_prompt: the user prompt
185
+ :param price_lookup: the price lookup
186
+ :param inference_service: the inference service
187
+ :param model: the model name
203
188
  """
204
- from edsl.jobs.JobsPrompts import JobsPrompts
205
-
206
189
  return JobsPrompts.estimate_prompt_cost(
207
190
  system_prompt, user_prompt, price_lookup, inference_service, model
208
191
  )
@@ -213,18 +196,14 @@ class Jobs(Base):
213
196
 
214
197
  :param iterations: the number of iterations to run
215
198
  """
216
- from edsl.jobs.JobsPrompts import JobsPrompts
217
-
218
- j = JobsPrompts(self)
219
- return j.estimate_job_cost(iterations)
199
+ return JobsPrompts(self).estimate_job_cost(iterations)
220
200
 
221
201
  def estimate_job_cost_from_external_prices(
222
202
  self, price_lookup: dict, iterations: int = 1
223
203
  ) -> dict:
224
- from edsl.jobs.JobsPrompts import JobsPrompts
225
-
226
- j = JobsPrompts(self)
227
- return j.estimate_job_cost_from_external_prices(price_lookup, iterations)
204
+ return JobsPrompts(self).estimate_job_cost_from_external_prices(
205
+ price_lookup, iterations
206
+ )
228
207
 
229
208
  @staticmethod
230
209
  def compute_job_cost(job_results: Results) -> float:
@@ -233,111 +212,14 @@ class Jobs(Base):
233
212
  """
234
213
  return job_results.compute_job_cost()
235
214
 
236
- @staticmethod
237
- def _get_container_class(object):
238
- from edsl.agents.AgentList import AgentList
239
- from edsl.agents.Agent import Agent
240
- from edsl.scenarios.Scenario import Scenario
241
- from edsl.scenarios.ScenarioList import ScenarioList
242
- from edsl.language_models.ModelList import ModelList
243
-
244
- if isinstance(object, Agent):
245
- return AgentList
246
- elif isinstance(object, Scenario):
247
- return ScenarioList
248
- elif isinstance(object, ModelList):
249
- return ModelList
250
- else:
251
- return list
252
-
253
- @staticmethod
254
- def _turn_args_to_list(args):
255
- """Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments.
256
-
257
- Example:
258
-
259
- >>> Jobs._turn_args_to_list([1,2,3])
260
- [1, 2, 3]
261
-
262
- """
263
-
264
- def did_user_pass_a_sequence(args):
265
- """Return True if the user passed a sequence, False otherwise.
266
-
267
- Example:
268
-
269
- >>> did_user_pass_a_sequence([1,2,3])
270
- True
271
-
272
- >>> did_user_pass_a_sequence(1)
273
- False
274
- """
275
- return len(args) == 1 and isinstance(args[0], Sequence)
276
-
277
- if did_user_pass_a_sequence(args):
278
- container_class = Jobs._get_container_class(args[0][0])
279
- return container_class(args[0])
280
- else:
281
- container_class = Jobs._get_container_class(args[0])
282
- return container_class(args)
283
-
284
- def _get_current_objects_of_this_type(
285
- self, object: Union["Agent", "Scenario", "LanguageModel"]
286
- ) -> tuple[list, str]:
215
+ def replace_missing_objects(self) -> None:
287
216
  from edsl.agents.Agent import Agent
217
+ from edsl.language_models.registry import Model
288
218
  from edsl.scenarios.Scenario import Scenario
289
- from edsl.language_models.LanguageModel import LanguageModel
290
-
291
- """Return the current objects of the same type as the first argument.
292
219
 
293
- >>> from edsl.jobs import Jobs
294
- >>> j = Jobs.example()
295
- >>> j._get_current_objects_of_this_type(j.agents[0])
296
- (AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'})]), 'agents')
297
- """
298
- class_to_key = {
299
- Agent: "agents",
300
- Scenario: "scenarios",
301
- LanguageModel: "models",
302
- }
303
- for class_type in class_to_key:
304
- if isinstance(object, class_type) or issubclass(
305
- object.__class__, class_type
306
- ):
307
- key = class_to_key[class_type]
308
- break
309
- else:
310
- raise ValueError(
311
- f"First argument must be an Agent, Scenario, or LanguageModel, not {object}"
312
- )
313
- current_objects = getattr(self, key, None)
314
- return current_objects, key
315
-
316
- @staticmethod
317
- def _get_empty_container_object(object):
318
- from edsl.agents.AgentList import AgentList
319
- from edsl.scenarios.ScenarioList import ScenarioList
320
-
321
- return {"Agent": AgentList([]), "Scenario": ScenarioList([])}.get(
322
- object.__class__.__name__, []
323
- )
324
-
325
- @staticmethod
326
- def _merge_objects(passed_objects, current_objects) -> list:
327
- """
328
- Combine all the existing objects with the new objects.
329
-
330
- For example, if the user passes in 3 agents,
331
- and there are 2 existing agents, this will create 6 new agents
332
-
333
- >>> Jobs(survey = [])._merge_objects([1,2,3], [4,5,6])
334
- [5, 6, 7, 6, 7, 8, 7, 8, 9]
335
- """
336
- new_objects = Jobs._get_empty_container_object(passed_objects[0])
337
- for current_object in current_objects:
338
- for new_object in passed_objects:
339
- new_objects.append(current_object + new_object)
340
- return new_objects
220
+ self.agents = self.agents or [Agent()]
221
+ self.models = self.models or [Model()]
222
+ self.scenarios = self.scenarios or [Scenario()]
341
223
 
342
224
  def interviews(self) -> list[Interview]:
343
225
  """
@@ -356,7 +238,12 @@ class Jobs(Base):
356
238
  if hasattr(self, "_interviews"):
357
239
  return self._interviews
358
240
  else:
359
- return list(self._create_interviews())
241
+ self.replace_missing_objects()
242
+ from edsl.jobs.InterviewsConstructor import InterviewsConstructor
243
+
244
+ self._interviews = list(InterviewsConstructor(self).create_interviews())
245
+
246
+ return self._interviews
360
247
 
361
248
  @classmethod
362
249
  def from_interviews(cls, interview_list):
@@ -373,34 +260,6 @@ class Jobs(Base):
373
260
  jobs._interviews = interview_list
374
261
  return jobs
375
262
 
376
- def _create_interviews(self) -> Generator[Interview, None, None]:
377
- """
378
- Generate interviews.
379
-
380
- Note that this sets the agents, model and scenarios if they have not been set. This is a side effect of the method.
381
- This is useful because a user can create a job without setting the agents, models, or scenarios, and the job will still run,
382
- with us filling in defaults.
383
-
384
-
385
- """
386
- # if no agents, models, or scenarios are set, set them to defaults
387
- from edsl.agents.Agent import Agent
388
- from edsl.language_models.registry import Model
389
- from edsl.scenarios.Scenario import Scenario
390
-
391
- self.agents = self.agents or [Agent()]
392
- self.models = self.models or [Model()]
393
- self.scenarios = self.scenarios or [Scenario()]
394
- for agent, scenario, model in product(self.agents, self.scenarios, self.models):
395
- yield Interview(
396
- survey=self.survey,
397
- agent=agent,
398
- scenario=scenario,
399
- model=model,
400
- skip_retry=self.skip_retry,
401
- raise_validation_errors=self.raise_validation_errors,
402
- )
403
-
404
263
  def create_bucket_collection(self) -> BucketCollection:
405
264
  """
406
265
  Create a collection of buckets for each model.
@@ -414,10 +273,8 @@ class Jobs(Base):
414
273
  >>> bc
415
274
  BucketCollection(...)
416
275
  """
417
- bucket_collection = BucketCollection()
418
- for model in self.models:
419
- bucket_collection.add_model(model)
420
- return bucket_collection
276
+ self.replace_missing_objects() # ensure that all objects are present
277
+ return BucketCollection.from_models(self.models)
421
278
 
422
279
  @property
423
280
  def bucket_collection(self) -> BucketCollection:
@@ -454,12 +311,20 @@ class Jobs(Base):
454
311
  if hasattr(self, "verbose") and self.verbose:
455
312
  print(message)
456
313
 
314
+ def all_question_parameters(self):
315
+ """Return all the fields in the questions in the survey.
316
+ >>> from edsl.jobs import Jobs
317
+ >>> Jobs.example().all_question_parameters()
318
+ {'period'}
319
+ """
320
+ return set.union(*[question.parameters for question in self.survey.questions])
321
+
457
322
  def _check_parameters(self, strict=False, warn=False) -> None:
458
323
  """Check if the parameters in the survey and scenarios are consistent.
459
324
 
460
- >>> from edsl import QuestionFreeText
461
- >>> from edsl import Survey
462
- >>> from edsl import Scenario
325
+ >>> from edsl.questions.QuestionFreeText import QuestionFreeText
326
+ >>> from edsl.surveys.Survey import Survey
327
+ >>> from edsl.scenarios.Scenario import Scenario
463
328
  >>> q = QuestionFreeText(question_text = "{{poo}}", question_name = "ugly_question")
464
329
  >>> j = Jobs(survey = Survey(questions=[q]))
465
330
  >>> with warnings.catch_warnings(record=True) as w:
@@ -514,7 +379,7 @@ class Jobs(Base):
514
379
  warnings.warn(
515
380
  "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."
516
381
  )
517
- self.scenarios = self.scenarios.convert_jinja_braces()
382
+ self.scenarios = self.scenarios._convert_jinja_braces()
518
383
 
519
384
  @property
520
385
  def skip_retry(self):
@@ -529,11 +394,13 @@ class Jobs(Base):
529
394
  return self._raise_validation_errors
530
395
 
531
396
  def use_remote_cache(self, disable_remote_cache: bool) -> bool:
397
+ import requests
398
+
532
399
  if disable_remote_cache:
533
400
  return False
534
401
  if not disable_remote_cache:
535
402
  try:
536
- from edsl import Coop
403
+ from edsl.coop.coop import Coop
537
404
 
538
405
  user_edsl_settings = Coop().edsl_settings
539
406
  return user_edsl_settings.get("remote_caching", False)
@@ -549,7 +416,7 @@ class Jobs(Base):
549
416
  n: int = 1,
550
417
  progress_bar: bool = False,
551
418
  stop_on_exception: bool = False,
552
- cache: Union[Cache, bool] = None,
419
+ cache: Union["Cache", bool] = None,
553
420
  check_api_keys: bool = False,
554
421
  sidecar_model: Optional[LanguageModel] = None,
555
422
  verbose: bool = True,
@@ -563,6 +430,7 @@ class Jobs(Base):
563
430
  raise_validation_errors: bool = False,
564
431
  disable_remote_cache: bool = False,
565
432
  disable_remote_inference: bool = False,
433
+ bucket_collection: Optional[BucketCollection] = None,
566
434
  ) -> Results:
567
435
  """
568
436
  Runs the Job: conducts Interviews and returns their results.
@@ -580,23 +448,20 @@ class Jobs(Base):
580
448
  :param disable_remote_inference: If True, the job will not use remote inference
581
449
  """
582
450
  from edsl.coop.coop import Coop
451
+ from edsl.jobs.JobsChecks import JobsChecks
452
+ from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
583
453
 
584
454
  self._check_parameters()
585
455
  self._skip_retry = skip_retry
586
456
  self._raise_validation_errors = raise_validation_errors
587
-
588
457
  self.verbose = verbose
589
458
 
590
- from edsl.jobs.JobsChecks import JobsChecks
591
-
592
459
  jc = JobsChecks(self)
593
460
 
594
461
  # check if the user has all the keys they need
595
462
  if jc.needs_key_process():
596
463
  jc.key_process()
597
464
 
598
- from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
599
-
600
465
  jh = JobsRemoteInferenceHandler(self, verbose=verbose)
601
466
  if jh.use_remote_inference(disable_remote_inference):
602
467
  jh.create_remote_inference_job(
@@ -620,6 +485,9 @@ class Jobs(Base):
620
485
 
621
486
  cache = Cache()
622
487
 
488
+ if bucket_collection is None:
489
+ bucket_collection = self.create_bucket_collection()
490
+
623
491
  remote_cache = self.use_remote_cache(disable_remote_cache)
624
492
  with RemoteCacheSync(
625
493
  coop=Coop(),
@@ -636,9 +504,8 @@ class Jobs(Base):
636
504
  sidecar_model=sidecar_model,
637
505
  print_exceptions=print_exceptions,
638
506
  raise_validation_errors=raise_validation_errors,
507
+ bucket_collection=bucket_collection,
639
508
  )
640
-
641
- # results.cache = cache.new_entries_cache()
642
509
  return results
643
510
 
644
511
  async def run_async(
@@ -650,6 +517,7 @@ class Jobs(Base):
650
517
  remote_inference_results_visibility: Optional[
651
518
  Literal["private", "public", "unlisted"]
652
519
  ] = "unlisted",
520
+ bucket_collection: Optional[BucketCollection] = None,
653
521
  **kwargs,
654
522
  ):
655
523
  """Run the job asynchronously, either locally or remotely.
@@ -664,6 +532,7 @@ class Jobs(Base):
664
532
  """
665
533
  # Check if we should use remote inference
666
534
  from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
535
+ from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
667
536
 
668
537
  jh = JobsRemoteInferenceHandler(self, verbose=False)
669
538
  if jh.use_remote_inference(disable_remote_inference):
@@ -674,40 +543,35 @@ class Jobs(Base):
674
543
  )
675
544
  return results
676
545
 
546
+ if bucket_collection is None:
547
+ bucket_collection = self.create_bucket_collection()
548
+
677
549
  # If not using remote inference, run locally with async
678
- return await JobsRunnerAsyncio(self).run_async(cache=cache, n=n, **kwargs)
550
+ return await JobsRunnerAsyncio(
551
+ self, bucket_collection=bucket_collection
552
+ ).run_async(cache=cache, n=n, **kwargs)
679
553
 
680
- def _run_local(self, *args, **kwargs):
554
+ def _run_local(self, bucket_collection, *args, **kwargs):
681
555
  """Run the job locally."""
556
+ from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
682
557
 
683
- results = JobsRunnerAsyncio(self).run(*args, **kwargs)
558
+ results = JobsRunnerAsyncio(self, bucket_collection=bucket_collection).run(
559
+ *args, **kwargs
560
+ )
684
561
  return results
685
562
 
686
- def all_question_parameters(self):
687
- """Return all the fields in the questions in the survey.
688
- >>> from edsl.jobs import Jobs
689
- >>> Jobs.example().all_question_parameters()
690
- {'period'}
691
- """
692
- return set.union(*[question.parameters for question in self.survey.questions])
693
-
694
563
  def __repr__(self) -> str:
695
564
  """Return an eval-able string representation of the Jobs instance."""
696
565
  return f"Jobs(survey={repr(self.survey)}, agents={repr(self.agents)}, models={repr(self.models)}, scenarios={repr(self.scenarios)})"
697
566
 
698
567
  def _summary(self):
699
568
  return {
700
- "EDSL Class": "Jobs",
701
- "Number of questions": len(self.survey),
702
- "Number of agents": len(self.agents),
703
- "Number of models": len(self.models),
704
- "Number of scenarios": len(self.scenarios),
569
+ "questions": len(self.survey),
570
+ "agents": len(self.agents or [1]),
571
+ "models": len(self.models or [1]),
572
+ "scenarios": len(self.scenarios or [1]),
705
573
  }
706
574
 
707
- def _repr_html_(self) -> str:
708
- footer = f"<a href={self.__documentation__}>(docs)</a>"
709
- return str(self.summary(format="html")) + footer
710
-
711
575
  def __len__(self) -> int:
712
576
  """Return the maximum number of questions that will be asked while running this job.
713
577
  Note that this is the maximum number of questions, not the actual number of questions that will be asked, as some questions may be skipped.
@@ -752,11 +616,14 @@ class Jobs(Base):
752
616
 
753
617
  return d
754
618
 
619
+ def table(self):
620
+ return self.prompts().to_scenario_list().table()
621
+
755
622
  @classmethod
756
623
  @remove_edsl_version
757
624
  def from_dict(cls, data: dict) -> Jobs:
758
625
  """Creates a Jobs instance from a dictionary."""
759
- from edsl import Survey
626
+ from edsl.surveys.Survey import Survey
760
627
  from edsl.agents.Agent import Agent
761
628
  from edsl.language_models.LanguageModel import LanguageModel
762
629
  from edsl.scenarios.Scenario import Scenario
@@ -800,14 +667,14 @@ class Jobs(Base):
800
667
  """
801
668
  import random
802
669
  from uuid import uuid4
803
- from edsl.questions import QuestionMultipleChoice
670
+ from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
804
671
  from edsl.agents.Agent import Agent
805
672
  from edsl.scenarios.Scenario import Scenario
806
673
 
807
674
  addition = "" if not randomize else str(uuid4())
808
675
 
809
676
  if test_model:
810
- from edsl.language_models import LanguageModel
677
+ from edsl.language_models.LanguageModel import LanguageModel
811
678
 
812
679
  m = LanguageModel.example(test_model=True)
813
680
 
@@ -848,7 +715,8 @@ class Jobs(Base):
848
715
  question_options=["Good", "Great", "OK", "Terrible"],
849
716
  question_name="how_feeling_yesterday",
850
717
  )
851
- from edsl import Survey, ScenarioList
718
+ from edsl.surveys.Survey import Survey
719
+ from edsl.scenarios.ScenarioList import ScenarioList
852
720
 
853
721
  base_survey = Survey(questions=[q1, q2])
854
722
 
@@ -865,15 +733,6 @@ class Jobs(Base):
865
733
 
866
734
  return job
867
735
 
868
- def rich_print(self):
869
- """Print a rich representation of the Jobs instance."""
870
- from rich.table import Table
871
-
872
- table = Table(title="Jobs")
873
- table.add_column("Jobs")
874
- table.add_row(self.survey.rich_print())
875
- return table
876
-
877
736
  def code(self):
878
737
  """Return the code to create this instance."""
879
738
  raise NotImplementedError
@@ -881,7 +740,7 @@ class Jobs(Base):
881
740
 
882
741
  def main():
883
742
  """Run the module's doctests."""
884
- from edsl.jobs import Jobs
743
+ from edsl.jobs.Jobs import Jobs
885
744
  from edsl.data.Cache import Cache
886
745
 
887
746
  job = Jobs.example()