edsl 0.1.39__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 (85) hide show
  1. edsl/Base.py +0 -28
  2. edsl/__init__.py +1 -1
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +17 -9
  5. edsl/agents/Invigilator.py +14 -13
  6. edsl/agents/InvigilatorBase.py +1 -4
  7. edsl/agents/PromptConstructor.py +22 -42
  8. edsl/agents/QuestionInstructionPromptBuilder.py +1 -1
  9. edsl/auto/AutoStudy.py +5 -18
  10. edsl/auto/StageBase.py +40 -53
  11. edsl/auto/StageQuestions.py +1 -2
  12. edsl/auto/utilities.py +6 -0
  13. edsl/coop/coop.py +5 -21
  14. edsl/data/Cache.py +18 -29
  15. edsl/data/CacheHandler.py +2 -0
  16. edsl/data/RemoteCacheSync.py +46 -154
  17. edsl/enums.py +0 -7
  18. edsl/inference_services/AnthropicService.py +16 -38
  19. edsl/inference_services/AvailableModelFetcher.py +1 -7
  20. edsl/inference_services/GoogleService.py +1 -5
  21. edsl/inference_services/InferenceServicesCollection.py +2 -18
  22. edsl/inference_services/OpenAIService.py +31 -46
  23. edsl/inference_services/TestService.py +3 -1
  24. edsl/inference_services/TogetherAIService.py +3 -5
  25. edsl/inference_services/data_structures.py +2 -74
  26. edsl/jobs/AnswerQuestionFunctionConstructor.py +113 -148
  27. edsl/jobs/FetchInvigilator.py +3 -10
  28. edsl/jobs/InterviewsConstructor.py +4 -6
  29. edsl/jobs/Jobs.py +233 -299
  30. edsl/jobs/JobsChecks.py +2 -2
  31. edsl/jobs/JobsPrompts.py +1 -1
  32. edsl/jobs/JobsRemoteInferenceHandler.py +136 -160
  33. edsl/jobs/interviews/Interview.py +42 -80
  34. edsl/jobs/runners/JobsRunnerAsyncio.py +358 -88
  35. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  36. edsl/jobs/tasks/TaskHistory.py +3 -24
  37. edsl/language_models/LanguageModel.py +4 -59
  38. edsl/language_models/ModelList.py +8 -19
  39. edsl/language_models/__init__.py +1 -1
  40. edsl/language_models/registry.py +180 -0
  41. edsl/language_models/repair.py +1 -1
  42. edsl/questions/QuestionBase.py +26 -35
  43. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +49 -52
  44. edsl/questions/QuestionBasePromptsMixin.py +1 -1
  45. edsl/questions/QuestionBudget.py +1 -1
  46. edsl/questions/QuestionCheckBox.py +2 -2
  47. edsl/questions/QuestionExtract.py +7 -5
  48. edsl/questions/QuestionFreeText.py +1 -1
  49. edsl/questions/QuestionList.py +15 -9
  50. edsl/questions/QuestionMatrix.py +1 -1
  51. edsl/questions/QuestionMultipleChoice.py +1 -1
  52. edsl/questions/QuestionNumerical.py +1 -1
  53. edsl/questions/QuestionRank.py +1 -1
  54. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +18 -6
  55. edsl/questions/{response_validator_factory.py → ResponseValidatorFactory.py} +1 -7
  56. edsl/questions/SimpleAskMixin.py +1 -1
  57. edsl/questions/__init__.py +1 -1
  58. edsl/results/DatasetExportMixin.py +119 -60
  59. edsl/results/Result.py +3 -109
  60. edsl/results/Results.py +39 -50
  61. edsl/scenarios/FileStore.py +0 -32
  62. edsl/scenarios/ScenarioList.py +7 -35
  63. edsl/scenarios/handlers/csv.py +0 -11
  64. edsl/surveys/Survey.py +20 -71
  65. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +1 -1
  66. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/RECORD +78 -84
  67. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +1 -1
  68. edsl/jobs/async_interview_runner.py +0 -138
  69. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  70. edsl/jobs/data_structures.py +0 -120
  71. edsl/jobs/results_exceptions_handler.py +0 -98
  72. edsl/language_models/model.py +0 -256
  73. edsl/questions/data_structures.py +0 -20
  74. edsl/results/file_exports.py +0 -252
  75. /edsl/agents/{question_option_processor.py → QuestionOptionProcessor.py} +0 -0
  76. /edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +0 -0
  77. /edsl/questions/{loop_processor.py → LoopProcessor.py} +0 -0
  78. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  79. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  80. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  81. /edsl/results/{results_selector.py → Selector.py} +0 -0
  82. /edsl/scenarios/{directory_scanner.py → DirectoryScanner.py} +0 -0
  83. /edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +0 -0
  84. /edsl/scenarios/{scenario_selector.py → ScenarioSelector.py} +0 -0
  85. {edsl-0.1.39.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
edsl/jobs/JobsChecks.py CHANGED
@@ -8,7 +8,7 @@ class JobsChecks:
8
8
  self.jobs = jobs
9
9
 
10
10
  def check_api_keys(self) -> None:
11
- from edsl.language_models.model import Model
11
+ from edsl.language_models.registry import Model
12
12
 
13
13
  if len(self.jobs.models) == 0:
14
14
  models = [Model()]
@@ -28,7 +28,7 @@ class JobsChecks:
28
28
  """
29
29
  missing_api_keys = set()
30
30
 
31
- from edsl.language_models.model import Model
31
+ from edsl.language_models.registry import Model
32
32
  from edsl.enums import service_to_api_keyname
33
33
 
34
34
  for model in self.jobs.models + [Model()]:
edsl/jobs/JobsPrompts.py CHANGED
@@ -51,7 +51,7 @@ class JobsPrompts:
51
51
  for interview_index, interview in enumerate(interviews):
52
52
  invigilators = [
53
53
  FetchInvigilator(interview)(question)
54
- for question in interview.survey.questions
54
+ for question in self.survey.questions
55
55
  ]
56
56
  for _, invigilator in enumerate(invigilators):
57
57
  prompts = invigilator.get_prompts()
@@ -1,6 +1,4 @@
1
- from typing import Optional, Union, Literal, TYPE_CHECKING, NewType, Callable, Any
2
-
3
- from dataclasses import dataclass
1
+ from typing import Optional, Union, Literal, TYPE_CHECKING, NewType
4
2
 
5
3
 
6
4
  Seconds = NewType("Seconds", float)
@@ -18,52 +16,26 @@ from edsl.coop.coop import RemoteInferenceResponse, RemoteInferenceCreationInfo
18
16
 
19
17
  from edsl.jobs.jobs_status_enums import JobsStatus
20
18
  from edsl.coop.utils import VisibilityType
21
- from edsl.jobs.JobsRemoteInferenceLogger import JobLogger
22
-
23
-
24
- class RemoteJobConstants:
25
- """Constants for remote job handling."""
26
-
27
- REMOTE_JOB_POLL_INTERVAL = 1
28
- REMOTE_JOB_VERBOSE = False
29
- DISCORD_URL = "https://discord.com/invite/mxAYkjfy9m"
30
-
31
-
32
- @dataclass
33
- class RemoteJobInfo:
34
- creation_data: RemoteInferenceCreationInfo
35
- job_uuid: JobUUID
36
- logger: JobLogger
37
19
 
38
20
 
39
21
  class JobsRemoteInferenceHandler:
40
- def __init__(
41
- self,
42
- jobs: "Jobs",
43
- verbose: bool = RemoteJobConstants.REMOTE_JOB_VERBOSE,
44
- poll_interval: Seconds = RemoteJobConstants.REMOTE_JOB_POLL_INTERVAL,
45
- ):
46
- """Handles the creation and running of a remote inference job."""
22
+ def __init__(self, jobs: "Jobs", verbose: bool = False, poll_interval: Seconds = 1):
23
+ """ """
47
24
  self.jobs = jobs
48
25
  self.verbose = verbose
49
26
  self.poll_interval = poll_interval
50
27
 
51
- from edsl.config import CONFIG
28
+ self._remote_job_creation_data: Union[None, RemoteInferenceCreationInfo] = None
29
+ self._job_uuid: Union[None, JobUUID] = None # Will be set when job is created
30
+ self.logger: Union[None, JobLogger] = None # Will be initialized when needed
52
31
 
53
- self.expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
54
- self.remote_inference_url = f"{self.expected_parrot_url}/home/remote-inference"
32
+ @property
33
+ def remote_job_creation_data(self) -> RemoteInferenceCreationInfo:
34
+ return self._remote_job_creation_data
55
35
 
56
- def _create_logger(self) -> JobLogger:
57
- from edsl.utilities.is_notebook import is_notebook
58
- from edsl.jobs.JobsRemoteInferenceLogger import (
59
- JupyterJobLogger,
60
- StdOutJobLogger,
61
- )
62
- from edsl.jobs.loggers.HTMLTableJobLogger import HTMLTableJobLogger
63
-
64
- if is_notebook():
65
- return HTMLTableJobLogger(verbose=self.verbose)
66
- return StdOutJobLogger(verbose=self.verbose)
36
+ @property
37
+ def job_uuid(self) -> JobUUID:
38
+ return self._job_uuid
67
39
 
68
40
  def use_remote_inference(self, disable_remote_inference: bool) -> bool:
69
41
  import requests
@@ -88,15 +60,23 @@ class JobsRemoteInferenceHandler:
88
60
  iterations: int = 1,
89
61
  remote_inference_description: Optional[str] = None,
90
62
  remote_inference_results_visibility: Optional[VisibilityType] = "unlisted",
91
- ) -> RemoteJobInfo:
92
-
63
+ ) -> None:
93
64
  from edsl.config import CONFIG
94
65
  from edsl.coop.coop import Coop
95
66
 
96
- logger = self._create_logger()
67
+ # Initialize logger
68
+ from edsl.utilities.is_notebook import is_notebook
69
+ from edsl.jobs.JobsRemoteInferenceLogger import JupyterJobLogger
70
+ from edsl.jobs.JobsRemoteInferenceLogger import StdOutJobLogger
71
+ from edsl.jobs.loggers.HTMLTableJobLogger import HTMLTableJobLogger
72
+
73
+ if is_notebook():
74
+ self.logger = HTMLTableJobLogger(verbose=self.verbose)
75
+ else:
76
+ self.logger = StdOutJobLogger(verbose=self.verbose)
97
77
 
98
78
  coop = Coop()
99
- logger.update(
79
+ self.logger.update(
100
80
  "Remote inference activated. Sending job to server...",
101
81
  status=JobsStatus.QUEUED,
102
82
  )
@@ -107,34 +87,33 @@ class JobsRemoteInferenceHandler:
107
87
  iterations=iterations,
108
88
  initial_results_visibility=remote_inference_results_visibility,
109
89
  )
110
- logger.update(
90
+ self.logger.update(
111
91
  "Your survey is running at the Expected Parrot server...",
112
92
  status=JobsStatus.RUNNING,
113
93
  )
94
+
114
95
  job_uuid = remote_job_creation_data.get("uuid")
115
- logger.update(
96
+ self.logger.update(
116
97
  message=f"Job sent to server. (Job uuid={job_uuid}).",
117
98
  status=JobsStatus.RUNNING,
118
99
  )
119
- logger.add_info("job_uuid", job_uuid)
100
+ self.logger.add_info("job_uuid", job_uuid)
120
101
 
121
- logger.update(
122
- f"Job details are available at your Coop account {self.remote_inference_url}",
102
+ expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
103
+ remote_inference_url = f"{expected_parrot_url}/home/remote-inference"
104
+
105
+ self.logger.update(
106
+ f"Job details are available at your Coop account {remote_inference_url}{remote_inference_url}",
123
107
  status=JobsStatus.RUNNING,
124
108
  )
125
- progress_bar_url = (
126
- f"{self.expected_parrot_url}/home/remote-job-progress/{job_uuid}"
127
- )
128
- logger.add_info("progress_bar_url", progress_bar_url)
129
- logger.update(
109
+ progress_bar_url = f"{expected_parrot_url}/home/remote-job-progress/{job_uuid}"
110
+ self.logger.add_info("progress_bar_url", progress_bar_url)
111
+ self.logger.update(
130
112
  f"View job progress here: {progress_bar_url}", status=JobsStatus.RUNNING
131
113
  )
132
114
 
133
- return RemoteJobInfo(
134
- creation_data=remote_job_creation_data,
135
- job_uuid=job_uuid,
136
- logger=logger,
137
- )
115
+ self._remote_job_creation_data = remote_job_creation_data
116
+ self._job_uuid = job_uuid
138
117
 
139
118
  @staticmethod
140
119
  def check_status(
@@ -145,127 +124,126 @@ class JobsRemoteInferenceHandler:
145
124
  coop = Coop()
146
125
  return coop.remote_inference_get(job_uuid)
147
126
 
148
- def _construct_remote_job_fetcher(
149
- self, testing_simulated_response: Optional[Any] = None
150
- ) -> Callable:
151
- if testing_simulated_response is not None:
152
- return lambda job_uuid: testing_simulated_response
153
- else:
154
- from edsl.coop.coop import Coop
155
-
156
- coop = Coop()
157
- return coop.remote_inference_get
158
-
159
- def _construct_object_fetcher(
160
- self, testing_simulated_response: Optional[Any] = None
161
- ) -> Callable:
162
- "Constructs a function to fetch the results object from Coop."
163
- if testing_simulated_response is not None:
164
- return lambda results_uuid, expected_object_type: Results.example()
165
- else:
166
- from edsl.coop.coop import Coop
167
-
168
- coop = Coop()
169
- return coop.get
170
-
171
- def _handle_cancelled_job(self, job_info: RemoteJobInfo) -> None:
172
- "Handles a cancelled job by logging the cancellation and updating the job status."
173
-
174
- job_info.logger.update(
175
- message="Job cancelled by the user.", status=JobsStatus.CANCELLED
176
- )
177
- job_info.logger.update(
178
- f"See {self.expected_parrot_url}/home/remote-inference for more details.",
179
- status=JobsStatus.CANCELLED,
180
- )
181
-
182
- def _handle_failed_job(
183
- self, job_info: RemoteJobInfo, remote_job_data: RemoteInferenceResponse
184
- ) -> None:
185
- "Handles a failed job by logging the error and updating the job status."
186
- latest_error_report_url = remote_job_data.get("latest_error_report_url")
187
- if latest_error_report_url:
188
- job_info.logger.add_info("error_report_url", latest_error_report_url)
189
-
190
- job_info.logger.update("Job failed.", status=JobsStatus.FAILED)
191
- job_info.logger.update(
192
- f"See {self.expected_parrot_url}/home/remote-inference for more details.",
193
- status=JobsStatus.FAILED,
194
- )
195
- job_info.logger.update(
196
- f"Need support? Visit Discord: {RemoteJobConstants.DISCORD_URL}",
197
- status=JobsStatus.FAILED,
127
+ def poll_remote_inference_job(self) -> Union[None, "Results"]:
128
+ return self._poll_remote_inference_job(
129
+ self.remote_job_creation_data, verbose=self.verbose
198
130
  )
199
131
 
200
- def _sleep_for_a_bit(self, job_info: RemoteJobInfo, status: str) -> None:
132
+ def _poll_remote_inference_job(
133
+ self,
134
+ remote_job_creation_data: RemoteInferenceCreationInfo,
135
+ verbose: bool = False,
136
+ poll_interval: Optional[Seconds] = None,
137
+ testing_simulated_response=None,
138
+ ) -> Union[None, "Results"]:
201
139
  import time
202
140
  from datetime import datetime
141
+ from edsl.config import CONFIG
142
+ from edsl.results.Results import Results
203
143
 
204
- time_checked = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
205
- job_info.logger.update(
206
- f"Job status: {status} - last update: {time_checked}",
207
- status=JobsStatus.RUNNING,
208
- )
209
- time.sleep(self.poll_interval)
144
+ if poll_interval is None:
145
+ poll_interval = self.poll_interval
210
146
 
211
- def _fetch_results_and_log(
212
- self,
213
- job_info: RemoteJobInfo,
214
- results_uuid: str,
215
- remote_job_data: RemoteInferenceResponse,
216
- object_fetcher: Callable,
217
- ) -> "Results":
218
- "Fetches the results object and logs the results URL."
219
- job_info.logger.add_info("results_uuid", results_uuid)
220
- results = object_fetcher(results_uuid, expected_object_type="results")
221
- results_url = remote_job_data.get("results_url")
222
- job_info.logger.update(
223
- f"Job completed and Results stored on Coop: {results_url}",
224
- status=JobsStatus.COMPLETED,
225
- )
226
- results.job_uuid = job_info.job_uuid
227
- results.results_uuid = results_uuid
228
- return results
147
+ job_uuid = remote_job_creation_data.get("uuid")
148
+ expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
229
149
 
230
- def poll_remote_inference_job(
231
- self,
232
- job_info: RemoteJobInfo,
233
- testing_simulated_response=None,
234
- ) -> Union[None, "Results"]:
235
- """Polls a remote inference job for completion and returns the results."""
150
+ if testing_simulated_response is not None:
151
+ remote_job_data_fetcher = lambda job_uuid: testing_simulated_response
152
+ object_fetcher = (
153
+ lambda results_uuid, expected_object_type: Results.example()
154
+ )
155
+ else:
156
+ from edsl.coop.coop import Coop
236
157
 
237
- remote_job_data_fetcher = self._construct_remote_job_fetcher(
238
- testing_simulated_response
239
- )
240
- object_fetcher = self._construct_object_fetcher(testing_simulated_response)
158
+ coop = Coop()
159
+ remote_job_data_fetcher = coop.remote_inference_get
160
+ object_fetcher = coop.get
241
161
 
242
162
  job_in_queue = True
243
163
  while job_in_queue:
244
- remote_job_data = remote_job_data_fetcher(job_info.job_uuid)
164
+ remote_job_data: RemoteInferenceResponse = remote_job_data_fetcher(job_uuid)
245
165
  status = remote_job_data.get("status")
246
166
 
247
167
  if status == "cancelled":
248
- self._handle_cancelled_job(job_info)
168
+ self.logger.update(
169
+ messaged="Job cancelled by the user.", status=JobsStatus.CANCELLED
170
+ )
171
+ self.logger.update(
172
+ f"See {expected_parrot_url}/home/remote-inference for more details.",
173
+ status=JobsStatus.CANCELLED,
174
+ )
249
175
  return None
250
176
 
251
- elif status == "failed" or status == "completed":
252
- if status == "failed":
253
- self._handle_failed_job(job_info, remote_job_data)
177
+ elif status == "failed":
178
+ latest_error_report_url = remote_job_data.get("latest_error_report_url")
179
+ if latest_error_report_url:
180
+ self.logger.update("Job failed.", status=JobsStatus.FAILED)
181
+ self.logger.update(
182
+ f"Error report: {latest_error_report_url}", "failed"
183
+ )
184
+ self.logger.add_info("error_report_url", latest_error_report_url)
185
+ self.logger.update(
186
+ "Need support? Visit Discord: https://discord.com/invite/mxAYkjfy9m",
187
+ status=JobsStatus.FAILED,
188
+ )
189
+ else:
190
+ self.logger.update("Job failed.", "failed")
191
+ self.logger.update(
192
+ f"See {expected_parrot_url}/home/remote-inference for details.",
193
+ status=JobsStatus.FAILED,
194
+ )
254
195
 
255
196
  results_uuid = remote_job_data.get("results_uuid")
256
197
  if results_uuid:
257
- results = self._fetch_results_and_log(
258
- job_info=job_info,
259
- results_uuid=results_uuid,
260
- remote_job_data=remote_job_data,
261
- object_fetcher=object_fetcher,
198
+ self.logger.add_info("results_uuid", results_uuid)
199
+ results = object_fetcher(
200
+ results_uuid, expected_object_type="results"
262
201
  )
202
+ results.job_uuid = job_uuid
203
+ results.results_uuid = results_uuid
263
204
  return results
264
205
  else:
265
206
  return None
266
207
 
208
+ elif status == "completed":
209
+ results_uuid = remote_job_data.get("results_uuid")
210
+ self.logger.add_info("results_uuid", results_uuid)
211
+ results_url = remote_job_data.get("results_url")
212
+ self.logger.add_info("results_url", results_url)
213
+ results = object_fetcher(results_uuid, expected_object_type="results")
214
+ self.logger.update(
215
+ f"Job completed and Results stored on Coop: {results_url}",
216
+ status=JobsStatus.COMPLETED,
217
+ )
218
+ results.job_uuid = job_uuid
219
+ results.results_uuid = results_uuid
220
+ return results
221
+
267
222
  else:
268
- self._sleep_for_a_bit(job_info, status)
223
+ time_checked = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
224
+ self.logger.update(
225
+ f"Job status: {status} - last update: {time_checked}",
226
+ status=JobsStatus.RUNNING,
227
+ )
228
+ time.sleep(poll_interval)
229
+
230
+ def use_remote_inference(self, disable_remote_inference: bool) -> bool:
231
+ import requests
232
+
233
+ if disable_remote_inference:
234
+ return False
235
+ if not disable_remote_inference:
236
+ try:
237
+ from edsl.coop.coop import Coop
238
+
239
+ user_edsl_settings = Coop().edsl_settings
240
+ return user_edsl_settings.get("remote_inference", False)
241
+ except requests.ConnectionError:
242
+ pass
243
+ except CoopServerResponseError as e:
244
+ pass
245
+
246
+ return False
269
247
 
270
248
  async def create_and_poll_remote_job(
271
249
  self,
@@ -287,7 +265,7 @@ class JobsRemoteInferenceHandler:
287
265
 
288
266
  # Create job using existing method
289
267
  loop = asyncio.get_event_loop()
290
- job_info = await loop.run_in_executor(
268
+ remote_job_creation_data = await loop.run_in_executor(
291
269
  None,
292
270
  partial(
293
271
  self.create_remote_inference_job,
@@ -296,12 +274,10 @@ class JobsRemoteInferenceHandler:
296
274
  remote_inference_results_visibility=remote_inference_results_visibility,
297
275
  ),
298
276
  )
299
- if job_info is None:
300
- raise ValueError("Remote job creation failed.")
301
277
 
278
+ # Poll using existing method but with async sleep
302
279
  return await loop.run_in_executor(
303
- None,
304
- partial(self.poll_remote_inference_job, job_info),
280
+ None, partial(self.poll_remote_inference_job, remote_job_creation_data)
305
281
  )
306
282
 
307
283