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.
- edsl/Base.py +169 -116
- edsl/__init__.py +14 -6
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +358 -146
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +88 -36
- edsl/agents/InvigilatorBase.py +59 -70
- edsl/agents/PromptConstructor.py +117 -219
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionOptionProcessor.py +172 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +104 -42
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +21 -14
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +33 -12
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +20 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +0 -3
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +209 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +2 -11
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +105 -80
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +1 -4
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -8
- edsl/inference_services/data_structures.py +62 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +40 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +48 -0
- edsl/jobs/Jobs.py +102 -243
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +5 -3
- edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +77 -380
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +14 -15
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +137 -234
- edsl/language_models/ModelList.py +11 -13
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +0 -1
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/registry.py +49 -59
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/AnswerValidatorMixin.py +47 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/LoopProcessor.py +149 -0
- edsl/questions/QuestionBase.py +37 -192
- edsl/questions/QuestionBaseGenMixin.py +52 -48
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/QuestionFreeText.py +1 -2
- edsl/questions/QuestionList.py +3 -5
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +66 -22
- edsl/questions/QuestionNumerical.py +1 -3
- edsl/questions/QuestionRank.py +6 -16
- edsl/questions/ResponseValidatorABC.py +37 -11
- edsl/questions/ResponseValidatorFactory.py +28 -0
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +1 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +224 -302
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +192 -206
- edsl/results/Results.py +120 -113
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/Selector.py +23 -13
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DirectoryScanner.py +96 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +118 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioJoin.py +10 -6
- edsl/scenarios/ScenarioList.py +383 -240
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/ScenarioSelector.py +156 -0
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +199 -771
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
- edsl-0.1.39.dev2.dist-info/RECORD +352 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
- {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.
|
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[
|
42
|
-
models: Optional[list[
|
43
|
-
scenarios: Optional[list[
|
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:
|
54
|
-
self.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
|
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
|
-
|
119
|
-
|
120
|
-
|
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.
|
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.
|
149
|
+
from edsl.jobs.JobsComponentConstructor import JobsComponentConstructor
|
148
150
|
|
149
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
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
|
-
|
294
|
-
|
295
|
-
|
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
|
-
|
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
|
-
|
418
|
-
|
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.
|
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(
|
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(
|
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
|
-
"
|
701
|
-
"
|
702
|
-
"
|
703
|
-
"
|
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
|
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()
|