edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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 +116 -197
- edsl/__init__.py +7 -15
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +147 -351
- edsl/agents/AgentList.py +73 -211
- edsl/agents/Invigilator.py +50 -101
- edsl/agents/InvigilatorBase.py +70 -62
- edsl/agents/PromptConstructor.py +225 -143
- edsl/agents/__init__.py +1 -0
- edsl/agents/prompt_helpers.py +3 -3
- edsl/auto/AutoStudy.py +5 -18
- edsl/auto/StageBase.py +40 -53
- edsl/auto/StageQuestions.py +1 -2
- edsl/auto/utilities.py +6 -0
- edsl/config.py +2 -22
- edsl/conversation/car_buying.py +1 -2
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +47 -125
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +27 -45
- edsl/data/CacheEntry.py +15 -12
- edsl/data/CacheHandler.py +12 -31
- edsl/data/RemoteCacheSync.py +46 -154
- edsl/data/__init__.py +3 -4
- edsl/data_transfer_models.py +1 -2
- edsl/enums.py +0 -27
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +0 -12
- edsl/exceptions/questions.py +6 -24
- edsl/exceptions/scenarios.py +0 -7
- edsl/inference_services/AnthropicService.py +19 -38
- edsl/inference_services/AwsBedrock.py +2 -0
- edsl/inference_services/AzureAI.py +2 -0
- edsl/inference_services/GoogleService.py +12 -7
- edsl/inference_services/InferenceServiceABC.py +85 -18
- edsl/inference_services/InferenceServicesCollection.py +79 -120
- edsl/inference_services/MistralAIService.py +3 -0
- edsl/inference_services/OpenAIService.py +35 -47
- edsl/inference_services/PerplexityService.py +3 -0
- edsl/inference_services/TestService.py +10 -11
- edsl/inference_services/TogetherAIService.py +3 -5
- edsl/jobs/Answers.py +14 -1
- edsl/jobs/Jobs.py +431 -356
- edsl/jobs/JobsChecks.py +10 -35
- edsl/jobs/JobsPrompts.py +4 -6
- edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
- edsl/jobs/buckets/BucketCollection.py +3 -44
- edsl/jobs/buckets/TokenBucket.py +21 -53
- edsl/jobs/interviews/Interview.py +408 -143
- edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
- edsl/jobs/runners/JobsRunnerStatus.py +165 -133
- edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
- edsl/jobs/tasks/TaskHistory.py +18 -38
- edsl/jobs/tasks/task_status_enum.py +2 -0
- edsl/language_models/KeyLookup.py +30 -0
- edsl/language_models/LanguageModel.py +236 -194
- edsl/language_models/ModelList.py +19 -28
- edsl/language_models/__init__.py +2 -1
- edsl/language_models/registry.py +190 -0
- edsl/language_models/repair.py +2 -2
- edsl/language_models/unused/ReplicateBase.py +83 -0
- edsl/language_models/utilities.py +4 -5
- edsl/notebooks/Notebook.py +14 -19
- edsl/prompts/Prompt.py +39 -29
- edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
- edsl/questions/QuestionBase.py +214 -68
- edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
- edsl/questions/QuestionBasePromptsMixin.py +3 -7
- edsl/questions/QuestionBudget.py +1 -1
- edsl/questions/QuestionCheckBox.py +3 -3
- edsl/questions/QuestionExtract.py +7 -5
- edsl/questions/QuestionFreeText.py +3 -2
- edsl/questions/QuestionList.py +18 -10
- edsl/questions/QuestionMultipleChoice.py +23 -67
- edsl/questions/QuestionNumerical.py +4 -2
- edsl/questions/QuestionRank.py +17 -7
- edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
- edsl/questions/SimpleAskMixin.py +3 -4
- edsl/questions/__init__.py +1 -2
- edsl/questions/derived/QuestionLinearScale.py +3 -6
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +3 -17
- edsl/questions/question_registry.py +1 -1
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +7 -170
- edsl/results/DatasetExportMixin.py +305 -168
- edsl/results/DatasetTree.py +8 -28
- edsl/results/Result.py +206 -298
- edsl/results/Results.py +131 -149
- edsl/results/ResultsDBMixin.py +238 -0
- edsl/results/ResultsExportMixin.py +0 -2
- edsl/results/{results_selector.py → Selector.py} +13 -23
- edsl/results/TableDisplay.py +171 -98
- edsl/results/__init__.py +1 -1
- edsl/scenarios/FileStore.py +239 -150
- edsl/scenarios/Scenario.py +193 -90
- edsl/scenarios/ScenarioHtmlMixin.py +3 -4
- edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
- edsl/scenarios/ScenarioList.py +244 -415
- edsl/scenarios/ScenarioListExportMixin.py +7 -0
- edsl/scenarios/ScenarioListPdfMixin.py +37 -15
- edsl/scenarios/__init__.py +2 -1
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +12 -5
- edsl/surveys/Rule.py +4 -5
- edsl/surveys/RuleCollection.py +27 -25
- edsl/surveys/Survey.py +791 -270
- edsl/surveys/SurveyCSS.py +8 -20
- edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
- edsl/surveys/__init__.py +2 -4
- edsl/surveys/descriptors.py +2 -6
- edsl/surveys/instructions/ChangeInstruction.py +2 -1
- edsl/surveys/instructions/Instruction.py +13 -4
- edsl/surveys/instructions/InstructionCollection.py +6 -11
- 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/utilities.py +23 -35
- {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
- edsl-0.1.39.dev1.dist-info/RECORD +277 -0
- {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
- edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
- edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
- edsl/agents/question_option_processor.py +0 -172
- edsl/coop/CoopFunctionsMixin.py +0 -15
- edsl/coop/ExpectedParrotKeyHandler.py +0 -125
- edsl/exceptions/inference_services.py +0 -5
- edsl/inference_services/AvailableModelCacheHandler.py +0 -184
- edsl/inference_services/AvailableModelFetcher.py +0 -215
- edsl/inference_services/ServiceAvailability.py +0 -135
- edsl/inference_services/data_structures.py +0 -134
- edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
- edsl/jobs/FetchInvigilator.py +0 -47
- edsl/jobs/InterviewTaskManager.py +0 -98
- edsl/jobs/InterviewsConstructor.py +0 -50
- edsl/jobs/JobsComponentConstructor.py +0 -189
- edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
- edsl/jobs/RequestTokenEstimator.py +0 -30
- edsl/jobs/async_interview_runner.py +0 -138
- edsl/jobs/buckets/TokenBucketAPI.py +0 -211
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/check_survey_scenario_compatibility.py +0 -85
- edsl/jobs/data_structures.py +0 -120
- edsl/jobs/decorators.py +0 -35
- edsl/jobs/jobs_status_enums.py +0 -9
- edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
- edsl/jobs/results_exceptions_handler.py +0 -98
- edsl/language_models/ComputeCost.py +0 -63
- edsl/language_models/PriceManager.py +0 -127
- edsl/language_models/RawResponseHandler.py +0 -106
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +0 -131
- edsl/language_models/model.py +0 -256
- edsl/notebooks/NotebookToLaTeX.py +0 -142
- edsl/questions/ExceptionExplainer.py +0 -77
- edsl/questions/HTMLQuestion.py +0 -103
- edsl/questions/QuestionMatrix.py +0 -265
- edsl/questions/data_structures.py +0 -20
- edsl/questions/loop_processor.py +0 -149
- edsl/questions/response_validator_factory.py +0 -34
- edsl/questions/templates/matrix/__init__.py +0 -1
- edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
- edsl/questions/templates/matrix/question_presentation.jinja +0 -20
- edsl/results/MarkdownToDocx.py +0 -122
- edsl/results/MarkdownToPDF.py +0 -111
- edsl/results/TextEditor.py +0 -50
- edsl/results/file_exports.py +0 -252
- edsl/results/smart_objects.py +0 -96
- edsl/results/table_data_class.py +0 -12
- edsl/results/table_renderers.py +0 -118
- edsl/scenarios/ConstructDownloadLink.py +0 -109
- edsl/scenarios/DocumentChunker.py +0 -102
- edsl/scenarios/DocxScenario.py +0 -16
- edsl/scenarios/PdfExtractor.py +0 -40
- edsl/scenarios/directory_scanner.py +0 -96
- edsl/scenarios/file_methods.py +0 -85
- edsl/scenarios/handlers/__init__.py +0 -13
- edsl/scenarios/handlers/csv.py +0 -49
- edsl/scenarios/handlers/docx.py +0 -76
- edsl/scenarios/handlers/html.py +0 -37
- edsl/scenarios/handlers/json.py +0 -111
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/scenarios/handlers/md.py +0 -51
- edsl/scenarios/handlers/pdf.py +0 -68
- edsl/scenarios/handlers/png.py +0 -39
- edsl/scenarios/handlers/pptx.py +0 -105
- edsl/scenarios/handlers/py.py +0 -294
- edsl/scenarios/handlers/sql.py +0 -313
- edsl/scenarios/handlers/sqlite.py +0 -149
- edsl/scenarios/handlers/txt.py +0 -33
- edsl/scenarios/scenario_selector.py +0 -156
- edsl/surveys/ConstructDAG.py +0 -92
- edsl/surveys/EditSurvey.py +0 -221
- edsl/surveys/InstructionHandler.py +0 -100
- edsl/surveys/MemoryManagement.py +0 -72
- edsl/surveys/RuleManager.py +0 -172
- edsl/surveys/Simulator.py +0 -75
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/utilities/PrettyList.py +0 -56
- edsl/utilities/is_notebook.py +0 -18
- edsl/utilities/is_valid_variable_name.py +0 -11
- edsl/utilities/remove_edsl_version.py +0 -24
- edsl-0.1.39.dist-info/RECORD +0 -358
- /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
- /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
- /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
- {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
edsl/jobs/Jobs.py
CHANGED
@@ -1,106 +1,36 @@
|
|
1
1
|
# """The Jobs class is a collection of agents, scenarios and models and one survey."""
|
2
2
|
from __future__ import annotations
|
3
|
-
import
|
4
|
-
|
5
|
-
from
|
6
|
-
|
7
|
-
Optional,
|
8
|
-
Union,
|
9
|
-
Sequence,
|
10
|
-
Generator,
|
11
|
-
TYPE_CHECKING,
|
12
|
-
Callable,
|
13
|
-
Tuple,
|
14
|
-
)
|
3
|
+
import warnings
|
4
|
+
import requests
|
5
|
+
from itertools import product
|
6
|
+
from typing import Literal, Optional, Union, Sequence, Generator, TYPE_CHECKING
|
15
7
|
|
16
8
|
from edsl.Base import Base
|
17
9
|
|
10
|
+
from edsl.exceptions import MissingAPIKeyError
|
18
11
|
from edsl.jobs.buckets.BucketCollection import BucketCollection
|
19
|
-
from edsl.jobs.JobsPrompts import JobsPrompts
|
20
12
|
from edsl.jobs.interviews.Interview import Interview
|
21
|
-
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
22
13
|
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
14
|
+
from edsl.utilities.decorators import remove_edsl_version
|
15
|
+
|
23
16
|
from edsl.data.RemoteCacheSync import RemoteCacheSync
|
24
17
|
from edsl.exceptions.coop import CoopServerResponseError
|
25
18
|
|
26
|
-
from edsl.jobs.JobsChecks import JobsChecks
|
27
|
-
from edsl.jobs.data_structures import RunEnvironment, RunParameters, RunConfig
|
28
|
-
|
29
19
|
if TYPE_CHECKING:
|
30
20
|
from edsl.agents.Agent import Agent
|
31
21
|
from edsl.agents.AgentList import AgentList
|
32
22
|
from edsl.language_models.LanguageModel import LanguageModel
|
33
23
|
from edsl.scenarios.Scenario import Scenario
|
34
|
-
from edsl.scenarios.ScenarioList import ScenarioList
|
35
24
|
from edsl.surveys.Survey import Survey
|
36
25
|
from edsl.results.Results import Results
|
37
26
|
from edsl.results.Dataset import Dataset
|
38
|
-
from edsl.language_models.ModelList import ModelList
|
39
|
-
from edsl.data.Cache import Cache
|
40
|
-
from edsl.language_models.key_management.KeyLookup import KeyLookup
|
41
|
-
|
42
|
-
VisibilityType = Literal["private", "public", "unlisted"]
|
43
|
-
|
44
|
-
from dataclasses import dataclass
|
45
|
-
from typing import Optional, Union, TypeVar, Callable, cast
|
46
|
-
from functools import wraps
|
47
|
-
|
48
|
-
try:
|
49
|
-
from typing import ParamSpec
|
50
|
-
except ImportError:
|
51
|
-
from typing_extensions import ParamSpec
|
52
|
-
|
53
|
-
|
54
|
-
P = ParamSpec("P")
|
55
|
-
T = TypeVar("T")
|
56
|
-
|
57
|
-
|
58
|
-
from edsl.jobs.check_survey_scenario_compatibility import (
|
59
|
-
CheckSurveyScenarioCompatibility,
|
60
|
-
)
|
61
|
-
|
62
|
-
|
63
|
-
def with_config(f: Callable[P, T]) -> Callable[P, T]:
|
64
|
-
"This decorator make it so that the run function parameters match the RunConfig dataclass."
|
65
|
-
parameter_fields = {
|
66
|
-
name: field.default
|
67
|
-
for name, field in RunParameters.__dataclass_fields__.items()
|
68
|
-
}
|
69
|
-
environment_fields = {
|
70
|
-
name: field.default
|
71
|
-
for name, field in RunEnvironment.__dataclass_fields__.items()
|
72
|
-
}
|
73
|
-
combined = {**parameter_fields, **environment_fields}
|
74
|
-
|
75
|
-
@wraps(f)
|
76
|
-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
77
|
-
environment = RunEnvironment(
|
78
|
-
**{k: v for k, v in kwargs.items() if k in environment_fields}
|
79
|
-
)
|
80
|
-
parameters = RunParameters(
|
81
|
-
**{k: v for k, v in kwargs.items() if k in parameter_fields}
|
82
|
-
)
|
83
|
-
config = RunConfig(environment=environment, parameters=parameters)
|
84
|
-
return f(*args, config=config)
|
85
|
-
|
86
|
-
# Update the wrapper's signature to include all RunConfig parameters
|
87
|
-
# old_sig = signature(f)
|
88
|
-
# wrapper.__signature__ = old_sig.replace(
|
89
|
-
# parameters=list(old_sig.parameters.values())[:-1]
|
90
|
-
# + [
|
91
|
-
# old_sig.parameters["config"].replace(
|
92
|
-
# default=parameter_fields[name], name=name
|
93
|
-
# )
|
94
|
-
# for name in combined
|
95
|
-
# ]
|
96
|
-
# )
|
97
|
-
|
98
|
-
return cast(Callable[P, T], wrapper)
|
99
27
|
|
100
28
|
|
101
29
|
class Jobs(Base):
|
102
30
|
"""
|
103
|
-
A collection of agents, scenarios and models and one survey
|
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.
|
104
34
|
"""
|
105
35
|
|
106
36
|
__documentation__ = "https://docs.expectedparrot.com/en/latest/jobs.html"
|
@@ -108,9 +38,9 @@ class Jobs(Base):
|
|
108
38
|
def __init__(
|
109
39
|
self,
|
110
40
|
survey: "Survey",
|
111
|
-
agents: Optional[
|
112
|
-
models: Optional[
|
113
|
-
scenarios: Optional[
|
41
|
+
agents: Optional[list["Agent"]] = None,
|
42
|
+
models: Optional[list["LanguageModel"]] = None,
|
43
|
+
scenarios: Optional[list["Scenario"]] = None,
|
114
44
|
):
|
115
45
|
"""Initialize a Jobs instance.
|
116
46
|
|
@@ -119,62 +49,14 @@ class Jobs(Base):
|
|
119
49
|
:param models: a list of models
|
120
50
|
:param scenarios: a list of scenarios
|
121
51
|
"""
|
122
|
-
self.run_config = RunConfig(
|
123
|
-
environment=RunEnvironment(), parameters=RunParameters()
|
124
|
-
)
|
125
|
-
|
126
52
|
self.survey = survey
|
127
|
-
self.agents: AgentList = agents
|
128
|
-
self.scenarios: ScenarioList = scenarios
|
129
|
-
self.models
|
130
|
-
|
131
|
-
def add_running_env(self, running_env: RunEnvironment):
|
132
|
-
self.run_config.add_environment(running_env)
|
133
|
-
return self
|
134
|
-
|
135
|
-
def using_cache(self, cache: "Cache") -> Jobs:
|
136
|
-
"""
|
137
|
-
Add a Cache to the job.
|
138
|
-
|
139
|
-
:param cache: the cache to add
|
140
|
-
"""
|
141
|
-
self.run_config.add_cache(cache)
|
142
|
-
return self
|
143
|
-
|
144
|
-
def using_bucket_collection(self, bucket_collection: BucketCollection) -> Jobs:
|
145
|
-
"""
|
146
|
-
Add a BucketCollection to the job.
|
147
|
-
|
148
|
-
:param bucket_collection: the bucket collection to add
|
149
|
-
"""
|
150
|
-
self.run_config.add_bucket_collection(bucket_collection)
|
151
|
-
return self
|
53
|
+
self.agents: "AgentList" = agents
|
54
|
+
self.scenarios: "ScenarioList" = scenarios
|
55
|
+
self.models = models
|
152
56
|
|
153
|
-
|
154
|
-
"""
|
155
|
-
Add a KeyLookup to the job.
|
156
|
-
|
157
|
-
:param key_lookup: the key lookup to add
|
158
|
-
"""
|
159
|
-
self.run_config.add_key_lookup(key_lookup)
|
160
|
-
return self
|
57
|
+
self.__bucket_collection = None
|
161
58
|
|
162
|
-
|
163
|
-
"""
|
164
|
-
Add a Cache, BucketCollection, or KeyLookup to the job.
|
165
|
-
|
166
|
-
:param obj: the object to add
|
167
|
-
"""
|
168
|
-
from edsl.data.Cache import Cache
|
169
|
-
from edsl.language_models.key_management.KeyLookup import KeyLookup
|
170
|
-
|
171
|
-
if isinstance(obj, Cache):
|
172
|
-
self.using_cache(obj)
|
173
|
-
elif isinstance(obj, BucketCollection):
|
174
|
-
self.using_bucket_collection(obj)
|
175
|
-
elif isinstance(obj, KeyLookup):
|
176
|
-
self.using_key_lookup(obj)
|
177
|
-
return self
|
59
|
+
# these setters and getters are used to ensure that the agents, models, and scenarios are stored as AgentList, ModelList, and ScenarioList objects
|
178
60
|
|
179
61
|
@property
|
180
62
|
def models(self):
|
@@ -182,7 +64,7 @@ class Jobs(Base):
|
|
182
64
|
|
183
65
|
@models.setter
|
184
66
|
def models(self, value):
|
185
|
-
from edsl
|
67
|
+
from edsl import ModelList
|
186
68
|
|
187
69
|
if value:
|
188
70
|
if not isinstance(value, ModelList):
|
@@ -192,19 +74,13 @@ class Jobs(Base):
|
|
192
74
|
else:
|
193
75
|
self._models = ModelList([])
|
194
76
|
|
195
|
-
# update the bucket collection if it exists
|
196
|
-
if self.run_config.environment.bucket_collection is None:
|
197
|
-
self.run_config.environment.bucket_collection = (
|
198
|
-
self.create_bucket_collection()
|
199
|
-
)
|
200
|
-
|
201
77
|
@property
|
202
78
|
def agents(self):
|
203
79
|
return self._agents
|
204
80
|
|
205
81
|
@agents.setter
|
206
82
|
def agents(self, value):
|
207
|
-
from edsl
|
83
|
+
from edsl import AgentList
|
208
84
|
|
209
85
|
if value:
|
210
86
|
if not isinstance(value, AgentList):
|
@@ -220,7 +96,7 @@ class Jobs(Base):
|
|
220
96
|
|
221
97
|
@scenarios.setter
|
222
98
|
def scenarios(self, value):
|
223
|
-
from edsl
|
99
|
+
from edsl import ScenarioList
|
224
100
|
from edsl.results.Dataset import Dataset
|
225
101
|
|
226
102
|
if value:
|
@@ -239,32 +115,28 @@ class Jobs(Base):
|
|
239
115
|
def by(
|
240
116
|
self,
|
241
117
|
*args: Union[
|
242
|
-
Agent,
|
243
|
-
Scenario,
|
244
|
-
LanguageModel,
|
118
|
+
"Agent",
|
119
|
+
"Scenario",
|
120
|
+
"LanguageModel",
|
245
121
|
Sequence[Union["Agent", "Scenario", "LanguageModel"]],
|
246
122
|
],
|
247
123
|
) -> Jobs:
|
248
124
|
"""
|
249
|
-
Add Agents, Scenarios and LanguageModels to a job.
|
250
|
-
|
251
|
-
:param args: objects or a sequence (list, tuple, ...) of objects of the same type
|
252
|
-
|
253
|
-
If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
|
254
|
-
Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
|
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.
|
255
126
|
|
256
127
|
This 'by' is intended to create a fluent interface.
|
257
128
|
|
258
|
-
>>> from edsl
|
259
|
-
>>> from edsl
|
129
|
+
>>> from edsl import Survey
|
130
|
+
>>> from edsl import QuestionFreeText
|
260
131
|
>>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
|
261
132
|
>>> j = Jobs(survey = Survey(questions=[q]))
|
262
133
|
>>> j
|
263
134
|
Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
|
264
|
-
>>> from edsl
|
135
|
+
>>> from edsl import Agent; a = Agent(traits = {"status": "Sad"})
|
265
136
|
>>> j.by(a).agents
|
266
137
|
AgentList([Agent(traits = {'status': 'Sad'})])
|
267
138
|
|
139
|
+
:param args: objects or a sequence (list, tuple, ...) of objects of the same type
|
268
140
|
|
269
141
|
Notes:
|
270
142
|
- all objects must implement the 'get_value', 'set_value', and `__add__` methods
|
@@ -272,9 +144,28 @@ class Jobs(Base):
|
|
272
144
|
- 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
|
273
145
|
- models: new models overwrite old models.
|
274
146
|
"""
|
275
|
-
from edsl.
|
147
|
+
from edsl.results.Dataset import Dataset
|
148
|
+
|
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
|
276
157
|
|
277
|
-
|
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
|
278
169
|
|
279
170
|
def prompts(self) -> "Dataset":
|
280
171
|
"""Return a Dataset of prompts that will be used.
|
@@ -284,9 +175,12 @@ class Jobs(Base):
|
|
284
175
|
>>> Jobs.example().prompts()
|
285
176
|
Dataset(...)
|
286
177
|
"""
|
287
|
-
|
178
|
+
from edsl.jobs.JobsPrompts import JobsPrompts
|
179
|
+
|
180
|
+
j = JobsPrompts(self)
|
181
|
+
return j.prompts()
|
288
182
|
|
289
|
-
def show_prompts(self, all
|
183
|
+
def show_prompts(self, all=False) -> None:
|
290
184
|
"""Print the prompts."""
|
291
185
|
if all:
|
292
186
|
return self.prompts().to_scenario_list().table()
|
@@ -306,12 +200,9 @@ class Jobs(Base):
|
|
306
200
|
"""
|
307
201
|
Estimate the cost of running the prompts.
|
308
202
|
:param iterations: the number of iterations to run
|
309
|
-
:param system_prompt: the system prompt
|
310
|
-
:param user_prompt: the user prompt
|
311
|
-
:param price_lookup: the price lookup
|
312
|
-
:param inference_service: the inference service
|
313
|
-
:param model: the model name
|
314
203
|
"""
|
204
|
+
from edsl.jobs.JobsPrompts import JobsPrompts
|
205
|
+
|
315
206
|
return JobsPrompts.estimate_prompt_cost(
|
316
207
|
system_prompt, user_prompt, price_lookup, inference_service, model
|
317
208
|
)
|
@@ -322,14 +213,18 @@ class Jobs(Base):
|
|
322
213
|
|
323
214
|
:param iterations: the number of iterations to run
|
324
215
|
"""
|
325
|
-
|
216
|
+
from edsl.jobs.JobsPrompts import JobsPrompts
|
217
|
+
|
218
|
+
j = JobsPrompts(self)
|
219
|
+
return j.estimate_job_cost(iterations)
|
326
220
|
|
327
221
|
def estimate_job_cost_from_external_prices(
|
328
222
|
self, price_lookup: dict, iterations: int = 1
|
329
223
|
) -> dict:
|
330
|
-
|
331
|
-
|
332
|
-
)
|
224
|
+
from edsl.jobs.JobsPrompts import JobsPrompts
|
225
|
+
|
226
|
+
j = JobsPrompts(self)
|
227
|
+
return j.estimate_job_cost_from_external_prices(price_lookup, iterations)
|
333
228
|
|
334
229
|
@staticmethod
|
335
230
|
def compute_job_cost(job_results: Results) -> float:
|
@@ -338,30 +233,111 @@ class Jobs(Base):
|
|
338
233
|
"""
|
339
234
|
return job_results.compute_job_cost()
|
340
235
|
|
341
|
-
|
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]:
|
342
287
|
from edsl.agents.Agent import Agent
|
343
|
-
from edsl.language_models.model import Model
|
344
288
|
from edsl.scenarios.Scenario import Scenario
|
289
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
345
290
|
|
346
|
-
|
347
|
-
self.models = self.models or [Model()]
|
348
|
-
self.scenarios = self.scenarios or [Scenario()]
|
291
|
+
"""Return the current objects of the same type as the first argument.
|
349
292
|
|
350
|
-
|
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')
|
351
297
|
"""
|
352
|
-
|
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
|
353
315
|
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
+
)
|
357
324
|
|
325
|
+
@staticmethod
|
326
|
+
def _merge_objects(passed_objects, current_objects) -> list:
|
358
327
|
"""
|
359
|
-
|
328
|
+
Combine all the existing objects with the new objects.
|
360
329
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
).
|
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
|
365
341
|
|
366
342
|
def interviews(self) -> list[Interview]:
|
367
343
|
"""
|
@@ -377,10 +353,13 @@ class Jobs(Base):
|
|
377
353
|
>>> j.interviews()[0]
|
378
354
|
Interview(agent = Agent(traits = {'status': 'Joyful'}), survey = Survey(...), scenario = Scenario({'period': 'morning'}), model = Model(...))
|
379
355
|
"""
|
380
|
-
|
356
|
+
if hasattr(self, "_interviews"):
|
357
|
+
return self._interviews
|
358
|
+
else:
|
359
|
+
return list(self._create_interviews())
|
381
360
|
|
382
361
|
@classmethod
|
383
|
-
def from_interviews(cls, interview_list)
|
362
|
+
def from_interviews(cls, interview_list):
|
384
363
|
"""Return a Jobs instance from a list of interviews.
|
385
364
|
|
386
365
|
This is useful when you have, say, a list of failed interviews and you want to create
|
@@ -394,6 +373,34 @@ class Jobs(Base):
|
|
394
373
|
jobs._interviews = interview_list
|
395
374
|
return jobs
|
396
375
|
|
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
|
+
|
397
404
|
def create_bucket_collection(self) -> BucketCollection:
|
398
405
|
"""
|
399
406
|
Create a collection of buckets for each model.
|
@@ -407,7 +414,17 @@ class Jobs(Base):
|
|
407
414
|
>>> bc
|
408
415
|
BucketCollection(...)
|
409
416
|
"""
|
410
|
-
|
417
|
+
bucket_collection = BucketCollection()
|
418
|
+
for model in self.models:
|
419
|
+
bucket_collection.add_model(model)
|
420
|
+
return bucket_collection
|
421
|
+
|
422
|
+
@property
|
423
|
+
def bucket_collection(self) -> BucketCollection:
|
424
|
+
"""Return the bucket collection. If it does not exist, create it."""
|
425
|
+
if self.__bucket_collection is None:
|
426
|
+
self.__bucket_collection = self.create_bucket_collection()
|
427
|
+
return self.__bucket_collection
|
411
428
|
|
412
429
|
def html(self):
|
413
430
|
"""Return the HTML representations for each scenario"""
|
@@ -434,27 +451,89 @@ class Jobs(Base):
|
|
434
451
|
|
435
452
|
def _output(self, message) -> None:
|
436
453
|
"""Check if a Job is verbose. If so, print the message."""
|
437
|
-
if self.
|
454
|
+
if hasattr(self, "verbose") and self.verbose:
|
438
455
|
print(message)
|
439
|
-
# if hasattr(self, "verbose") and self.verbose:
|
440
|
-
# print(message)
|
441
456
|
|
442
|
-
def
|
443
|
-
"""
|
444
|
-
|
445
|
-
>>>
|
446
|
-
|
447
|
-
|
448
|
-
|
457
|
+
def _check_parameters(self, strict=False, warn=False) -> None:
|
458
|
+
"""Check if the parameters in the survey and scenarios are consistent.
|
459
|
+
|
460
|
+
>>> from edsl import QuestionFreeText
|
461
|
+
>>> from edsl import Survey
|
462
|
+
>>> from edsl import Scenario
|
463
|
+
>>> q = QuestionFreeText(question_text = "{{poo}}", question_name = "ugly_question")
|
464
|
+
>>> j = Jobs(survey = Survey(questions=[q]))
|
465
|
+
>>> with warnings.catch_warnings(record=True) as w:
|
466
|
+
... j._check_parameters(warn = True)
|
467
|
+
... assert len(w) == 1
|
468
|
+
... assert issubclass(w[-1].category, UserWarning)
|
469
|
+
... assert "The following parameters are in the survey but not in the scenarios" in str(w[-1].message)
|
470
|
+
|
471
|
+
>>> q = QuestionFreeText(question_text = "{{poo}}", question_name = "ugly_question")
|
472
|
+
>>> s = Scenario({'plop': "A", 'poo': "B"})
|
473
|
+
>>> j = Jobs(survey = Survey(questions=[q])).by(s)
|
474
|
+
>>> j._check_parameters(strict = True)
|
475
|
+
Traceback (most recent call last):
|
476
|
+
...
|
477
|
+
ValueError: The following parameters are in the scenarios but not in the survey: {'plop'}
|
478
|
+
|
479
|
+
>>> q = QuestionFreeText(question_text = "Hello", question_name = "ugly_question")
|
480
|
+
>>> s = Scenario({'ugly_question': "B"})
|
481
|
+
>>> j = Jobs(survey = Survey(questions=[q])).by(s)
|
482
|
+
>>> j._check_parameters()
|
483
|
+
Traceback (most recent call last):
|
484
|
+
...
|
485
|
+
ValueError: The following names are in both the survey question_names and the scenario keys: {'ugly_question'}. This will create issues.
|
486
|
+
"""
|
487
|
+
survey_parameters: set = self.survey.parameters
|
488
|
+
scenario_parameters: set = self.scenarios.parameters
|
489
|
+
|
490
|
+
msg0, msg1, msg2 = None, None, None
|
491
|
+
|
492
|
+
# look for key issues
|
493
|
+
if intersection := set(self.scenarios.parameters) & set(
|
494
|
+
self.survey.question_names
|
495
|
+
):
|
496
|
+
msg0 = f"The following names are in both the survey question_names and the scenario keys: {intersection}. This will create issues."
|
449
497
|
|
450
|
-
|
451
|
-
import requests
|
498
|
+
raise ValueError(msg0)
|
452
499
|
|
453
|
-
if
|
500
|
+
if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
|
501
|
+
msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
|
502
|
+
if in_scenarios_but_not_in_survey := scenario_parameters - survey_parameters:
|
503
|
+
msg2 = f"The following parameters are in the scenarios but not in the survey: {in_scenarios_but_not_in_survey}"
|
504
|
+
|
505
|
+
if msg1 or msg2:
|
506
|
+
message = "\n".join(filter(None, [msg1, msg2]))
|
507
|
+
if strict:
|
508
|
+
raise ValueError(message)
|
509
|
+
else:
|
510
|
+
if warn:
|
511
|
+
warnings.warn(message)
|
512
|
+
|
513
|
+
if self.scenarios.has_jinja_braces:
|
514
|
+
warnings.warn(
|
515
|
+
"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
|
+
)
|
517
|
+
self.scenarios = self.scenarios.convert_jinja_braces()
|
518
|
+
|
519
|
+
@property
|
520
|
+
def skip_retry(self):
|
521
|
+
if not hasattr(self, "_skip_retry"):
|
454
522
|
return False
|
455
|
-
|
523
|
+
return self._skip_retry
|
524
|
+
|
525
|
+
@property
|
526
|
+
def raise_validation_errors(self):
|
527
|
+
if not hasattr(self, "_raise_validation_errors"):
|
528
|
+
return False
|
529
|
+
return self._raise_validation_errors
|
530
|
+
|
531
|
+
def use_remote_cache(self, disable_remote_cache: bool) -> bool:
|
532
|
+
if disable_remote_cache:
|
533
|
+
return False
|
534
|
+
if not disable_remote_cache:
|
456
535
|
try:
|
457
|
-
from edsl
|
536
|
+
from edsl import Coop
|
458
537
|
|
459
538
|
user_edsl_settings = Coop().edsl_settings
|
460
539
|
return user_edsl_settings.get("remote_caching", False)
|
@@ -465,173 +544,152 @@ class Jobs(Base):
|
|
465
544
|
|
466
545
|
return False
|
467
546
|
|
468
|
-
def
|
547
|
+
def run(
|
469
548
|
self,
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
"
|
489
|
-
|
490
|
-
|
491
|
-
def _check_if_remote_keys_ok(self):
|
492
|
-
jc = JobsChecks(self)
|
493
|
-
if jc.needs_key_process():
|
494
|
-
jc.key_process()
|
495
|
-
|
496
|
-
def _check_if_local_keys_ok(self):
|
497
|
-
jc = JobsChecks(self)
|
498
|
-
if self.run_config.parameters.check_api_keys:
|
499
|
-
jc.check_api_keys()
|
500
|
-
|
501
|
-
async def _execute_with_remote_cache(self, run_job_async: bool) -> Results:
|
502
|
-
|
503
|
-
use_remote_cache = self.use_remote_cache()
|
549
|
+
n: int = 1,
|
550
|
+
progress_bar: bool = False,
|
551
|
+
stop_on_exception: bool = False,
|
552
|
+
cache: Union[Cache, bool] = None,
|
553
|
+
check_api_keys: bool = False,
|
554
|
+
sidecar_model: Optional[LanguageModel] = None,
|
555
|
+
verbose: bool = True,
|
556
|
+
print_exceptions=True,
|
557
|
+
remote_cache_description: Optional[str] = None,
|
558
|
+
remote_inference_description: Optional[str] = None,
|
559
|
+
remote_inference_results_visibility: Optional[
|
560
|
+
Literal["private", "public", "unlisted"]
|
561
|
+
] = "unlisted",
|
562
|
+
skip_retry: bool = False,
|
563
|
+
raise_validation_errors: bool = False,
|
564
|
+
disable_remote_cache: bool = False,
|
565
|
+
disable_remote_inference: bool = False,
|
566
|
+
) -> Results:
|
567
|
+
"""
|
568
|
+
Runs the Job: conducts Interviews and returns their results.
|
504
569
|
|
570
|
+
:param n: How many times to run each interview
|
571
|
+
:param progress_bar: Whether to show a progress bar
|
572
|
+
:param stop_on_exception: Stops the job if an exception is raised
|
573
|
+
:param cache: A Cache object to store results
|
574
|
+
:param check_api_keys: Raises an error if API keys are invalid
|
575
|
+
:param verbose: Prints extra messages
|
576
|
+
:param remote_cache_description: Specifies a description for this group of entries in the remote cache
|
577
|
+
:param remote_inference_description: Specifies a description for the remote inference job
|
578
|
+
:param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
|
579
|
+
:param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
|
580
|
+
:param disable_remote_inference: If True, the job will not use remote inference
|
581
|
+
"""
|
505
582
|
from edsl.coop.coop import Coop
|
506
|
-
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
507
|
-
from edsl.data.Cache import Cache
|
508
583
|
|
509
|
-
|
584
|
+
self._check_parameters()
|
585
|
+
self._skip_retry = skip_retry
|
586
|
+
self._raise_validation_errors = raise_validation_errors
|
510
587
|
|
511
|
-
|
512
|
-
coop=Coop(),
|
513
|
-
cache=self.run_config.environment.cache,
|
514
|
-
output_func=self._output,
|
515
|
-
remote_cache=use_remote_cache,
|
516
|
-
remote_cache_description=self.run_config.parameters.remote_cache_description,
|
517
|
-
):
|
518
|
-
runner = JobsRunnerAsyncio(self, environment=self.run_config.environment)
|
519
|
-
if run_job_async:
|
520
|
-
results = await runner.run_async(self.run_config.parameters)
|
521
|
-
else:
|
522
|
-
results = runner.run(self.run_config.parameters)
|
523
|
-
return results
|
588
|
+
self.verbose = verbose
|
524
589
|
|
525
|
-
|
590
|
+
from edsl.jobs.JobsChecks import JobsChecks
|
526
591
|
|
527
|
-
self
|
528
|
-
self._check_if_remote_keys_ok()
|
529
|
-
|
530
|
-
# first try to run the job remotely
|
531
|
-
if results := self._remote_results():
|
532
|
-
return results
|
533
|
-
|
534
|
-
self._check_if_local_keys_ok()
|
535
|
-
return None
|
592
|
+
jc = JobsChecks(self)
|
536
593
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
return len(self)
|
541
|
-
else:
|
542
|
-
len(self) * self.run_config.parameters.n
|
594
|
+
# check if the user has all the keys they need
|
595
|
+
if jc.needs_key_process():
|
596
|
+
jc.key_process()
|
543
597
|
|
544
|
-
|
545
|
-
"Shared code for run and run_async"
|
546
|
-
if config.environment.cache is not None:
|
547
|
-
self.run_config.environment.cache = config.environment.cache
|
598
|
+
from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
|
548
599
|
|
549
|
-
|
550
|
-
|
551
|
-
|
600
|
+
jh = JobsRemoteInferenceHandler(self, verbose=verbose)
|
601
|
+
if jh.use_remote_inference(disable_remote_inference):
|
602
|
+
jh.create_remote_inference_job(
|
603
|
+
iterations=n,
|
604
|
+
remote_inference_description=remote_inference_description,
|
605
|
+
remote_inference_results_visibility=remote_inference_results_visibility,
|
552
606
|
)
|
607
|
+
results = jh.poll_remote_inference_job()
|
608
|
+
return results
|
553
609
|
|
554
|
-
if
|
555
|
-
|
556
|
-
|
557
|
-
# replace the parameters with the ones from the config
|
558
|
-
self.run_config.parameters = config.parameters
|
559
|
-
|
560
|
-
self.replace_missing_objects()
|
561
|
-
|
562
|
-
# try to run remotely first
|
563
|
-
self._prepare_to_run()
|
564
|
-
self._check_if_remote_keys_ok()
|
610
|
+
if check_api_keys:
|
611
|
+
jc.check_api_keys()
|
565
612
|
|
566
|
-
|
567
|
-
|
568
|
-
or self.run_config.environment.cache is True
|
569
|
-
):
|
613
|
+
# handle cache
|
614
|
+
if cache is None or cache is True:
|
570
615
|
from edsl.data.CacheHandler import CacheHandler
|
571
616
|
|
572
|
-
|
573
|
-
|
574
|
-
if self.run_config.environment.cache is False:
|
617
|
+
cache = CacheHandler().get_cache()
|
618
|
+
if cache is False:
|
575
619
|
from edsl.data.Cache import Cache
|
576
620
|
|
577
|
-
|
621
|
+
cache = Cache()
|
578
622
|
|
579
|
-
|
580
|
-
|
581
|
-
|
623
|
+
remote_cache = self.use_remote_cache(disable_remote_cache)
|
624
|
+
with RemoteCacheSync(
|
625
|
+
coop=Coop(),
|
626
|
+
cache=cache,
|
627
|
+
output_func=self._output,
|
628
|
+
remote_cache=remote_cache,
|
629
|
+
remote_cache_description=remote_cache_description,
|
630
|
+
) as r:
|
631
|
+
results = self._run_local(
|
632
|
+
n=n,
|
633
|
+
progress_bar=progress_bar,
|
634
|
+
cache=cache,
|
635
|
+
stop_on_exception=stop_on_exception,
|
636
|
+
sidecar_model=sidecar_model,
|
637
|
+
print_exceptions=print_exceptions,
|
638
|
+
raise_validation_errors=raise_validation_errors,
|
639
|
+
)
|
582
640
|
|
583
|
-
|
641
|
+
# results.cache = cache.new_entries_cache()
|
642
|
+
return results
|
584
643
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
644
|
+
async def run_async(
|
645
|
+
self,
|
646
|
+
cache=None,
|
647
|
+
n=1,
|
648
|
+
disable_remote_inference: bool = False,
|
649
|
+
remote_inference_description: Optional[str] = None,
|
650
|
+
remote_inference_results_visibility: Optional[
|
651
|
+
Literal["private", "public", "unlisted"]
|
652
|
+
] = "unlisted",
|
653
|
+
**kwargs,
|
654
|
+
):
|
655
|
+
"""Run the job asynchronously, either locally or remotely.
|
589
656
|
|
590
|
-
|
591
|
-
|
657
|
+
:param cache: Cache object or boolean
|
658
|
+
:param n: Number of iterations
|
659
|
+
:param disable_remote_inference: If True, forces local execution
|
660
|
+
:param remote_inference_description: Description for remote jobs
|
661
|
+
:param remote_inference_results_visibility: Visibility setting for remote results
|
662
|
+
:param kwargs: Additional arguments passed to local execution
|
663
|
+
:return: Results object
|
592
664
|
"""
|
593
|
-
|
665
|
+
# Check if we should use remote inference
|
666
|
+
from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
|
594
667
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
:param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
|
604
|
-
:param disable_remote_inference: If True, the job will not use remote inference
|
605
|
-
:param cache: A Cache object to store results
|
606
|
-
:param bucket_collection: A BucketCollection object to track API calls
|
607
|
-
:param key_lookup: A KeyLookup object to manage API keys
|
608
|
-
"""
|
609
|
-
self._run(config)
|
668
|
+
jh = JobsRemoteInferenceHandler(self, verbose=False)
|
669
|
+
if jh.use_remote_inference(disable_remote_inference):
|
670
|
+
results = await jh.create_and_poll_remote_job(
|
671
|
+
iterations=n,
|
672
|
+
remote_inference_description=remote_inference_description,
|
673
|
+
remote_inference_results_visibility=remote_inference_results_visibility,
|
674
|
+
)
|
675
|
+
return results
|
610
676
|
|
611
|
-
|
677
|
+
# If not using remote inference, run locally with async
|
678
|
+
return await JobsRunnerAsyncio(self).run_async(cache=cache, n=n, **kwargs)
|
612
679
|
|
613
|
-
|
614
|
-
|
615
|
-
"""
|
616
|
-
Runs the Job: conducts Interviews and returns their results.
|
680
|
+
def _run_local(self, *args, **kwargs):
|
681
|
+
"""Run the job locally."""
|
617
682
|
|
618
|
-
|
619
|
-
|
620
|
-
:param stop_on_exception: Stops the job if an exception is raised
|
621
|
-
:param check_api_keys: Raises an error if API keys are invalid
|
622
|
-
:param verbose: Prints extra messages
|
623
|
-
:param remote_cache_description: Specifies a description for this group of entries in the remote cache
|
624
|
-
:param remote_inference_description: Specifies a description for the remote inference job
|
625
|
-
:param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
|
626
|
-
:param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
|
627
|
-
:param disable_remote_inference: If True, the job will not use remote inference
|
628
|
-
:param cache: A Cache object to store results
|
629
|
-
:param bucket_collection: A BucketCollection object to track API calls
|
630
|
-
:param key_lookup: A KeyLookup object to manage API keys
|
631
|
-
"""
|
632
|
-
self._run(config)
|
683
|
+
results = JobsRunnerAsyncio(self).run(*args, **kwargs)
|
684
|
+
return results
|
633
685
|
|
634
|
-
|
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])
|
635
693
|
|
636
694
|
def __repr__(self) -> str:
|
637
695
|
"""Return an eval-able string representation of the Jobs instance."""
|
@@ -639,12 +697,17 @@ class Jobs(Base):
|
|
639
697
|
|
640
698
|
def _summary(self):
|
641
699
|
return {
|
642
|
-
"
|
643
|
-
"
|
644
|
-
"
|
645
|
-
"
|
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),
|
646
705
|
}
|
647
706
|
|
707
|
+
def _repr_html_(self) -> str:
|
708
|
+
footer = f"<a href={self.__documentation__}>(docs)</a>"
|
709
|
+
return str(self.summary(format="html")) + footer
|
710
|
+
|
648
711
|
def __len__(self) -> int:
|
649
712
|
"""Return the maximum number of questions that will be asked while running this job.
|
650
713
|
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.
|
@@ -661,6 +724,10 @@ class Jobs(Base):
|
|
661
724
|
)
|
662
725
|
return number_of_questions
|
663
726
|
|
727
|
+
#######################
|
728
|
+
# Serialization methods
|
729
|
+
#######################
|
730
|
+
|
664
731
|
def to_dict(self, add_edsl_version=True):
|
665
732
|
d = {
|
666
733
|
"survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
|
@@ -685,14 +752,11 @@ class Jobs(Base):
|
|
685
752
|
|
686
753
|
return d
|
687
754
|
|
688
|
-
def table(self):
|
689
|
-
return self.prompts().to_scenario_list().table()
|
690
|
-
|
691
755
|
@classmethod
|
692
756
|
@remove_edsl_version
|
693
757
|
def from_dict(cls, data: dict) -> Jobs:
|
694
758
|
"""Creates a Jobs instance from a dictionary."""
|
695
|
-
from edsl
|
759
|
+
from edsl import Survey
|
696
760
|
from edsl.agents.Agent import Agent
|
697
761
|
from edsl.language_models.LanguageModel import LanguageModel
|
698
762
|
from edsl.scenarios.Scenario import Scenario
|
@@ -714,6 +778,9 @@ class Jobs(Base):
|
|
714
778
|
"""
|
715
779
|
return hash(self) == hash(other)
|
716
780
|
|
781
|
+
#######################
|
782
|
+
# Example methods
|
783
|
+
#######################
|
717
784
|
@classmethod
|
718
785
|
def example(
|
719
786
|
cls,
|
@@ -733,14 +800,14 @@ class Jobs(Base):
|
|
733
800
|
"""
|
734
801
|
import random
|
735
802
|
from uuid import uuid4
|
736
|
-
from edsl.questions
|
803
|
+
from edsl.questions import QuestionMultipleChoice
|
737
804
|
from edsl.agents.Agent import Agent
|
738
805
|
from edsl.scenarios.Scenario import Scenario
|
739
806
|
|
740
807
|
addition = "" if not randomize else str(uuid4())
|
741
808
|
|
742
809
|
if test_model:
|
743
|
-
from edsl.language_models
|
810
|
+
from edsl.language_models import LanguageModel
|
744
811
|
|
745
812
|
m = LanguageModel.example(test_model=True)
|
746
813
|
|
@@ -781,8 +848,7 @@ class Jobs(Base):
|
|
781
848
|
question_options=["Good", "Great", "OK", "Terrible"],
|
782
849
|
question_name="how_feeling_yesterday",
|
783
850
|
)
|
784
|
-
from edsl
|
785
|
-
from edsl.scenarios.ScenarioList import ScenarioList
|
851
|
+
from edsl import Survey, ScenarioList
|
786
852
|
|
787
853
|
base_survey = Survey(questions=[q1, q2])
|
788
854
|
|
@@ -799,6 +865,15 @@ class Jobs(Base):
|
|
799
865
|
|
800
866
|
return job
|
801
867
|
|
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
|
+
|
802
877
|
def code(self):
|
803
878
|
"""Return the code to create this instance."""
|
804
879
|
raise NotImplementedError
|
@@ -806,7 +881,7 @@ class Jobs(Base):
|
|
806
881
|
|
807
882
|
def main():
|
808
883
|
"""Run the module's doctests."""
|
809
|
-
from edsl.jobs
|
884
|
+
from edsl.jobs import Jobs
|
810
885
|
from edsl.data.Cache import Cache
|
811
886
|
|
812
887
|
job = Jobs.example()
|