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/JobsChecks.py
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
import os
|
2
|
-
from edsl.exceptions import MissingAPIKeyError
|
2
|
+
from edsl.exceptions.general import MissingAPIKeyError
|
3
3
|
|
4
4
|
|
5
5
|
class JobsChecks:
|
6
6
|
def __init__(self, jobs):
|
7
|
-
""" """
|
7
|
+
"""Checks a Jobs object for missing API keys and other requirements."""
|
8
8
|
self.jobs = jobs
|
9
9
|
|
10
10
|
def check_api_keys(self) -> None:
|
11
|
-
from edsl import Model
|
11
|
+
from edsl.language_models.registry import Model
|
12
12
|
|
13
|
-
|
13
|
+
if len(self.jobs.models) == 0:
|
14
|
+
models = [Model()]
|
15
|
+
else:
|
16
|
+
models = self.jobs.models
|
17
|
+
|
18
|
+
for model in models: # + [Model()]:
|
14
19
|
if not model.has_valid_api_key():
|
15
20
|
raise MissingAPIKeyError(
|
16
21
|
model_name=str(model.model),
|
@@ -23,7 +28,7 @@ class JobsChecks:
|
|
23
28
|
"""
|
24
29
|
missing_api_keys = set()
|
25
30
|
|
26
|
-
from edsl import Model
|
31
|
+
from edsl.language_models.registry import Model
|
27
32
|
from edsl.enums import service_to_api_keyname
|
28
33
|
|
29
34
|
for model in self.jobs.models + [Model()]:
|
@@ -95,16 +100,33 @@ class JobsChecks:
|
|
95
100
|
return True
|
96
101
|
|
97
102
|
def needs_key_process(self):
|
103
|
+
"""
|
104
|
+
A User needs the key process when:
|
105
|
+
1. They don't have all the model keys
|
106
|
+
2. They don't have the EP API
|
107
|
+
3. They need external LLMs to run the job
|
108
|
+
"""
|
98
109
|
return (
|
99
110
|
not self.user_has_all_model_keys()
|
100
111
|
and not self.user_has_ep_api_key()
|
101
112
|
and self.needs_external_llms()
|
102
113
|
)
|
103
114
|
|
115
|
+
def status(self) -> dict:
|
116
|
+
"""
|
117
|
+
Returns a dictionary with the status of the job checks.
|
118
|
+
"""
|
119
|
+
return {
|
120
|
+
"user_has_ep_api_key": self.user_has_ep_api_key(),
|
121
|
+
"user_has_all_model_keys": self.user_has_all_model_keys(),
|
122
|
+
"needs_external_llms": self.needs_external_llms(),
|
123
|
+
"needs_key_process": self.needs_key_process(),
|
124
|
+
}
|
125
|
+
|
104
126
|
def key_process(self):
|
105
127
|
import secrets
|
106
128
|
from dotenv import load_dotenv
|
107
|
-
from edsl import CONFIG
|
129
|
+
from edsl.config import CONFIG
|
108
130
|
from edsl.coop.coop import Coop
|
109
131
|
from edsl.utilities.utilities import write_api_key_to_env
|
110
132
|
|
@@ -119,10 +141,12 @@ class JobsChecks:
|
|
119
141
|
"\nYou can either add the missing keys to your .env file, or use remote inference."
|
120
142
|
)
|
121
143
|
print("Remote inference allows you to run jobs on our server.")
|
122
|
-
print("\n🚀 To use remote inference, sign up at the following link:")
|
123
144
|
|
124
145
|
coop = Coop()
|
125
|
-
coop._display_login_url(
|
146
|
+
coop._display_login_url(
|
147
|
+
edsl_auth_token=edsl_auth_token,
|
148
|
+
link_description="\n🚀 To use remote inference, sign up at the following link:",
|
149
|
+
)
|
126
150
|
|
127
151
|
print(
|
128
152
|
"\nOnce you log in, we will automatically retrieve your Expected Parrot API key and continue your job remotely."
|
@@ -134,8 +158,9 @@ class JobsChecks:
|
|
134
158
|
print("\nTimed out waiting for login. Please try again.")
|
135
159
|
return
|
136
160
|
|
137
|
-
write_api_key_to_env(api_key)
|
138
|
-
print("✨ API key retrieved and written to .env file
|
161
|
+
path_to_env = write_api_key_to_env(api_key)
|
162
|
+
print("\n✨ API key retrieved and written to .env file at the following path:")
|
163
|
+
print(f" {path_to_env}")
|
139
164
|
|
140
165
|
# Retrieve API key so we can continue running the job
|
141
166
|
load_dotenv()
|
@@ -0,0 +1,189 @@
|
|
1
|
+
from typing import Union, Sequence, TYPE_CHECKING
|
2
|
+
|
3
|
+
if TYPE_CHECKING:
|
4
|
+
from edsl.agents.Agent import Agent
|
5
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
6
|
+
from edsl.scenarios.Scenario import Scenario
|
7
|
+
from edsl.jobs.Jobs import Jobs
|
8
|
+
|
9
|
+
|
10
|
+
class JobsComponentConstructor:
|
11
|
+
"Handles the creation of Agents, Scenarios, and LanguageModels in a job."
|
12
|
+
|
13
|
+
def __init__(self, jobs: "Jobs"):
|
14
|
+
self.jobs = jobs
|
15
|
+
|
16
|
+
def by(
|
17
|
+
self,
|
18
|
+
*args: Union[
|
19
|
+
"Agent",
|
20
|
+
"Scenario",
|
21
|
+
"LanguageModel",
|
22
|
+
Sequence[Union["Agent", "Scenario", "LanguageModel"]],
|
23
|
+
],
|
24
|
+
) -> "Jobs":
|
25
|
+
"""
|
26
|
+
Add Agents, Scenarios and LanguageModels to a job.
|
27
|
+
|
28
|
+
:param args: objects or a sequence (list, tuple, ...) of objects of the same type
|
29
|
+
|
30
|
+
If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
|
31
|
+
Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
|
32
|
+
|
33
|
+
This 'by' is intended to create a fluent interface.
|
34
|
+
|
35
|
+
>>> from edsl.surveys import Survey
|
36
|
+
>>> from edsl.questions import QuestionFreeText
|
37
|
+
>>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
|
38
|
+
>>> from edsl.jobs import Jobs
|
39
|
+
>>> j = Jobs(survey = Survey(questions=[q]))
|
40
|
+
>>> j
|
41
|
+
Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
|
42
|
+
>>> from edsl import Agent; a = Agent(traits = {"status": "Sad"})
|
43
|
+
>>> j.by(a).agents
|
44
|
+
AgentList([Agent(traits = {'status': 'Sad'})])
|
45
|
+
|
46
|
+
|
47
|
+
Notes:
|
48
|
+
- all objects must implement the 'get_value', 'set_value', and `__add__` methods
|
49
|
+
- agents: traits of new agents are combined with traits of existing agents. New and existing agents should not have overlapping traits, and do not increase the # agents in the instance
|
50
|
+
- 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
|
51
|
+
- models: new models overwrite old models.
|
52
|
+
"""
|
53
|
+
from edsl.results.Dataset import Dataset
|
54
|
+
|
55
|
+
if isinstance(
|
56
|
+
args[0], Dataset
|
57
|
+
): # let the user use a Dataset as if it were a ScenarioList
|
58
|
+
args = args[0].to_scenario_list()
|
59
|
+
|
60
|
+
passed_objects = self._turn_args_to_list(
|
61
|
+
args
|
62
|
+
) # objects can also be passed comma-separated
|
63
|
+
|
64
|
+
current_objects, objects_key = self._get_current_objects_of_this_type(
|
65
|
+
passed_objects[0]
|
66
|
+
)
|
67
|
+
|
68
|
+
if not current_objects:
|
69
|
+
new_objects = passed_objects
|
70
|
+
else:
|
71
|
+
new_objects = self._merge_objects(passed_objects, current_objects)
|
72
|
+
|
73
|
+
setattr(self.jobs, objects_key, new_objects) # update the job object
|
74
|
+
return self.jobs
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def _turn_args_to_list(args):
|
78
|
+
"""Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments.
|
79
|
+
|
80
|
+
Example:
|
81
|
+
|
82
|
+
>>> JobsComponentConstructor._turn_args_to_list([1,2,3])
|
83
|
+
[1, 2, 3]
|
84
|
+
|
85
|
+
"""
|
86
|
+
|
87
|
+
def did_user_pass_a_sequence(args):
|
88
|
+
"""Return True if the user passed a sequence, False otherwise.
|
89
|
+
|
90
|
+
Example:
|
91
|
+
|
92
|
+
>>> did_user_pass_a_sequence([1,2,3])
|
93
|
+
True
|
94
|
+
|
95
|
+
>>> did_user_pass_a_sequence(1)
|
96
|
+
False
|
97
|
+
"""
|
98
|
+
return len(args) == 1 and isinstance(args[0], Sequence)
|
99
|
+
|
100
|
+
if did_user_pass_a_sequence(args):
|
101
|
+
container_class = JobsComponentConstructor._get_container_class(args[0][0])
|
102
|
+
return container_class(args[0])
|
103
|
+
else:
|
104
|
+
container_class = JobsComponentConstructor._get_container_class(args[0])
|
105
|
+
return container_class(args)
|
106
|
+
|
107
|
+
def _get_current_objects_of_this_type(
|
108
|
+
self, object: Union["Agent", "Scenario", "LanguageModel"]
|
109
|
+
) -> tuple[list, str]:
|
110
|
+
from edsl.agents.Agent import Agent
|
111
|
+
from edsl.scenarios.Scenario import Scenario
|
112
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
113
|
+
|
114
|
+
"""Return the current objects of the same type as the first argument.
|
115
|
+
|
116
|
+
>>> from edsl.jobs import Jobs
|
117
|
+
>>> j = JobsComponentConstructor(Jobs.example())
|
118
|
+
>>> j._get_current_objects_of_this_type(j.agents[0])
|
119
|
+
(AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'})]), 'agents')
|
120
|
+
"""
|
121
|
+
class_to_key = {
|
122
|
+
Agent: "agents",
|
123
|
+
Scenario: "scenarios",
|
124
|
+
LanguageModel: "models",
|
125
|
+
}
|
126
|
+
for class_type in class_to_key:
|
127
|
+
if isinstance(object, class_type) or issubclass(
|
128
|
+
object.__class__, class_type
|
129
|
+
):
|
130
|
+
key = class_to_key[class_type]
|
131
|
+
break
|
132
|
+
else:
|
133
|
+
raise ValueError(
|
134
|
+
f"First argument must be an Agent, Scenario, or LanguageModel, not {object}"
|
135
|
+
)
|
136
|
+
current_objects = getattr(self.jobs, key, None)
|
137
|
+
return current_objects, key
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def _get_empty_container_object(object):
|
141
|
+
from edsl.agents.AgentList import AgentList
|
142
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
143
|
+
|
144
|
+
return {"Agent": AgentList([]), "Scenario": ScenarioList([])}.get(
|
145
|
+
object.__class__.__name__, []
|
146
|
+
)
|
147
|
+
|
148
|
+
@staticmethod
|
149
|
+
def _merge_objects(passed_objects, current_objects) -> list:
|
150
|
+
"""
|
151
|
+
Combine all the existing objects with the new objects.
|
152
|
+
|
153
|
+
For example, if the user passes in 3 agents,
|
154
|
+
and there are 2 existing agents, this will create 6 new agents
|
155
|
+
>>> from edsl.jobs import Jobs
|
156
|
+
>>> JobsComponentConstructor(Jobs(survey = []))._merge_objects([1,2,3], [4,5,6])
|
157
|
+
[5, 6, 7, 6, 7, 8, 7, 8, 9]
|
158
|
+
"""
|
159
|
+
new_objects = JobsComponentConstructor._get_empty_container_object(
|
160
|
+
passed_objects[0]
|
161
|
+
)
|
162
|
+
for current_object in current_objects:
|
163
|
+
for new_object in passed_objects:
|
164
|
+
new_objects.append(current_object + new_object)
|
165
|
+
return new_objects
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def _get_container_class(object):
|
169
|
+
from edsl.agents.AgentList import AgentList
|
170
|
+
from edsl.agents.Agent import Agent
|
171
|
+
from edsl.scenarios.Scenario import Scenario
|
172
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
173
|
+
from edsl.language_models.ModelList import ModelList
|
174
|
+
|
175
|
+
if isinstance(object, Agent):
|
176
|
+
return AgentList
|
177
|
+
elif isinstance(object, Scenario):
|
178
|
+
return ScenarioList
|
179
|
+
elif isinstance(object, ModelList):
|
180
|
+
return ModelList
|
181
|
+
else:
|
182
|
+
return list
|
183
|
+
|
184
|
+
|
185
|
+
if __name__ == "__main__":
|
186
|
+
"""Run the module's doctests."""
|
187
|
+
import doctest
|
188
|
+
|
189
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
edsl/jobs/JobsPrompts.py
CHANGED
@@ -11,6 +11,8 @@ if TYPE_CHECKING:
|
|
11
11
|
# from edsl.scenarios.ScenarioList import ScenarioList
|
12
12
|
# from edsl.surveys.Survey import Survey
|
13
13
|
|
14
|
+
from edsl.jobs.FetchInvigilator import FetchInvigilator
|
15
|
+
|
14
16
|
|
15
17
|
class JobsPrompts:
|
16
18
|
def __init__(self, jobs: "Jobs"):
|
@@ -23,7 +25,7 @@ class JobsPrompts:
|
|
23
25
|
@property
|
24
26
|
def price_lookup(self):
|
25
27
|
if self._price_lookup is None:
|
26
|
-
from edsl import Coop
|
28
|
+
from edsl.coop.coop import Coop
|
27
29
|
|
28
30
|
c = Coop()
|
29
31
|
self._price_lookup = c.fetch_prices()
|
@@ -48,7 +50,7 @@ class JobsPrompts:
|
|
48
50
|
|
49
51
|
for interview_index, interview in enumerate(interviews):
|
50
52
|
invigilators = [
|
51
|
-
interview
|
53
|
+
FetchInvigilator(interview)(question)
|
52
54
|
for question in self.survey.questions
|
53
55
|
]
|
54
56
|
for _, invigilator in enumerate(invigilators):
|
@@ -184,7 +186,7 @@ class JobsPrompts:
|
|
184
186
|
data = []
|
185
187
|
for interview in interviews:
|
186
188
|
invigilators = [
|
187
|
-
interview
|
189
|
+
FetchInvigilator(interview)(question)
|
188
190
|
for question in self.survey.questions
|
189
191
|
]
|
190
192
|
for invigilator in invigilators:
|
@@ -1,47 +1,50 @@
|
|
1
|
-
from typing import Optional, Union, Literal
|
2
|
-
|
3
|
-
|
1
|
+
from typing import Optional, Union, Literal, TYPE_CHECKING, NewType
|
2
|
+
|
3
|
+
|
4
|
+
Seconds = NewType("Seconds", float)
|
5
|
+
JobUUID = NewType("JobUUID", str)
|
6
|
+
|
4
7
|
from edsl.exceptions.coop import CoopServerResponseError
|
5
8
|
|
6
|
-
|
7
|
-
from edsl.results import Results
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from edsl.results.Results import Results
|
11
|
+
from edsl.jobs.Jobs import Jobs
|
12
|
+
from edsl.coop.coop import RemoteInferenceResponse, RemoteInferenceCreationInfo
|
13
|
+
from edsl.jobs.JobsRemoteInferenceLogger import JobLogger
|
14
|
+
|
15
|
+
from edsl.coop.coop import RemoteInferenceResponse, RemoteInferenceCreationInfo
|
16
|
+
|
17
|
+
from edsl.jobs.jobs_status_enums import JobsStatus
|
18
|
+
from edsl.coop.utils import VisibilityType
|
8
19
|
|
9
20
|
|
10
21
|
class JobsRemoteInferenceHandler:
|
11
|
-
def __init__(self, jobs, verbose=False, poll_interval=
|
12
|
-
"""
|
13
|
-
>>> from edsl.jobs import Jobs
|
14
|
-
>>> jh = JobsRemoteInferenceHandler(Jobs.example(), verbose=True)
|
15
|
-
>>> jh.use_remote_inference(True)
|
16
|
-
False
|
17
|
-
>>> jh._poll_remote_inference_job({'uuid':1234}, testing_simulated_response={"status": "failed"}) # doctest: +NORMALIZE_WHITESPACE
|
18
|
-
Job failed.
|
19
|
-
...
|
20
|
-
>>> jh._poll_remote_inference_job({'uuid':1234}, testing_simulated_response={"status": "completed"}) # doctest: +NORMALIZE_WHITESPACE
|
21
|
-
Job completed and Results stored on Coop: None.
|
22
|
-
Results(...)
|
23
|
-
"""
|
22
|
+
def __init__(self, jobs: "Jobs", verbose: bool = False, poll_interval: Seconds = 1):
|
23
|
+
""" """
|
24
24
|
self.jobs = jobs
|
25
25
|
self.verbose = verbose
|
26
26
|
self.poll_interval = poll_interval
|
27
27
|
|
28
|
-
self._remote_job_creation_data = None
|
29
|
-
self._job_uuid = None
|
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
|
30
31
|
|
31
32
|
@property
|
32
|
-
def remote_job_creation_data(self):
|
33
|
+
def remote_job_creation_data(self) -> RemoteInferenceCreationInfo:
|
33
34
|
return self._remote_job_creation_data
|
34
35
|
|
35
36
|
@property
|
36
|
-
def job_uuid(self):
|
37
|
+
def job_uuid(self) -> JobUUID:
|
37
38
|
return self._job_uuid
|
38
39
|
|
39
40
|
def use_remote_inference(self, disable_remote_inference: bool) -> bool:
|
41
|
+
import requests
|
42
|
+
|
40
43
|
if disable_remote_inference:
|
41
44
|
return False
|
42
45
|
if not disable_remote_inference:
|
43
46
|
try:
|
44
|
-
from edsl import Coop
|
47
|
+
from edsl.coop.coop import Coop
|
45
48
|
|
46
49
|
user_edsl_settings = Coop().edsl_settings
|
47
50
|
return user_edsl_settings.get("remote_inference", False)
|
@@ -56,16 +59,27 @@ class JobsRemoteInferenceHandler:
|
|
56
59
|
self,
|
57
60
|
iterations: int = 1,
|
58
61
|
remote_inference_description: Optional[str] = None,
|
59
|
-
remote_inference_results_visibility: Optional[
|
60
|
-
|
61
|
-
):
|
62
|
-
""" """
|
62
|
+
remote_inference_results_visibility: Optional[VisibilityType] = "unlisted",
|
63
|
+
) -> None:
|
63
64
|
from edsl.config import CONFIG
|
64
65
|
from edsl.coop.coop import Coop
|
65
|
-
|
66
|
+
|
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)
|
66
77
|
|
67
78
|
coop = Coop()
|
68
|
-
|
79
|
+
self.logger.update(
|
80
|
+
"Remote inference activated. Sending job to server...",
|
81
|
+
status=JobsStatus.QUEUED,
|
82
|
+
)
|
69
83
|
remote_job_creation_data = coop.remote_inference_create(
|
70
84
|
self.jobs,
|
71
85
|
description=remote_inference_description,
|
@@ -73,51 +87,65 @@ class JobsRemoteInferenceHandler:
|
|
73
87
|
iterations=iterations,
|
74
88
|
initial_results_visibility=remote_inference_results_visibility,
|
75
89
|
)
|
90
|
+
self.logger.update(
|
91
|
+
"Your survey is running at the Expected Parrot server...",
|
92
|
+
status=JobsStatus.RUNNING,
|
93
|
+
)
|
94
|
+
|
76
95
|
job_uuid = remote_job_creation_data.get("uuid")
|
77
|
-
|
96
|
+
self.logger.update(
|
97
|
+
message=f"Job sent to server. (Job uuid={job_uuid}).",
|
98
|
+
status=JobsStatus.RUNNING,
|
99
|
+
)
|
100
|
+
self.logger.add_info("job_uuid", job_uuid)
|
78
101
|
|
79
102
|
expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
|
80
|
-
|
103
|
+
remote_inference_url = f"{expected_parrot_url}/home/remote-inference"
|
81
104
|
|
82
|
-
|
83
|
-
f"
|
105
|
+
self.logger.update(
|
106
|
+
f"Job details are available at your Coop account {remote_inference_url}{remote_inference_url}",
|
107
|
+
status=JobsStatus.RUNNING,
|
108
|
+
)
|
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(
|
112
|
+
f"View job progress here: {progress_bar_url}", status=JobsStatus.RUNNING
|
84
113
|
)
|
85
114
|
|
86
115
|
self._remote_job_creation_data = remote_job_creation_data
|
87
116
|
self._job_uuid = job_uuid
|
88
|
-
# return remote_job_creation_data
|
89
117
|
|
90
118
|
@staticmethod
|
91
|
-
def check_status(
|
119
|
+
def check_status(
|
120
|
+
job_uuid: JobUUID,
|
121
|
+
) -> RemoteInferenceResponse:
|
92
122
|
from edsl.coop.coop import Coop
|
93
123
|
|
94
124
|
coop = Coop()
|
95
125
|
return coop.remote_inference_get(job_uuid)
|
96
126
|
|
97
|
-
def poll_remote_inference_job(self):
|
127
|
+
def poll_remote_inference_job(self) -> Union[None, "Results"]:
|
98
128
|
return self._poll_remote_inference_job(
|
99
129
|
self.remote_job_creation_data, verbose=self.verbose
|
100
130
|
)
|
101
131
|
|
102
132
|
def _poll_remote_inference_job(
|
103
133
|
self,
|
104
|
-
remote_job_creation_data:
|
105
|
-
verbose=False,
|
106
|
-
poll_interval: Optional[
|
107
|
-
testing_simulated_response
|
108
|
-
) -> Union[
|
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"]:
|
109
139
|
import time
|
110
140
|
from datetime import datetime
|
111
141
|
from edsl.config import CONFIG
|
112
|
-
from edsl.
|
142
|
+
from edsl.results.Results import Results
|
113
143
|
|
114
144
|
if poll_interval is None:
|
115
145
|
poll_interval = self.poll_interval
|
116
146
|
|
117
|
-
expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
|
118
|
-
|
119
147
|
job_uuid = remote_job_creation_data.get("uuid")
|
120
|
-
|
148
|
+
expected_parrot_url = CONFIG.get("EXPECTED_PARROT_URL")
|
121
149
|
|
122
150
|
if testing_simulated_response is not None:
|
123
151
|
remote_job_data_fetcher = lambda job_uuid: testing_simulated_response
|
@@ -125,66 +153,88 @@ class JobsRemoteInferenceHandler:
|
|
125
153
|
lambda results_uuid, expected_object_type: Results.example()
|
126
154
|
)
|
127
155
|
else:
|
156
|
+
from edsl.coop.coop import Coop
|
157
|
+
|
158
|
+
coop = Coop()
|
128
159
|
remote_job_data_fetcher = coop.remote_inference_get
|
129
160
|
object_fetcher = coop.get
|
130
161
|
|
131
162
|
job_in_queue = True
|
132
163
|
while job_in_queue:
|
133
|
-
remote_job_data = remote_job_data_fetcher(job_uuid)
|
164
|
+
remote_job_data: RemoteInferenceResponse = remote_job_data_fetcher(job_uuid)
|
134
165
|
status = remote_job_data.get("status")
|
166
|
+
|
135
167
|
if status == "cancelled":
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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,
|
140
174
|
)
|
141
175
|
return None
|
176
|
+
|
142
177
|
elif status == "failed":
|
143
|
-
print("\r" + " " * 80 + "\r", end="")
|
144
|
-
# write to stderr
|
145
178
|
latest_error_report_url = remote_job_data.get("latest_error_report_url")
|
146
179
|
if latest_error_report_url:
|
147
|
-
|
148
|
-
|
149
|
-
f"
|
180
|
+
self.logger.update("Job failed.", status=JobsStatus.FAILED)
|
181
|
+
self.logger.update(
|
182
|
+
f"Error report: {latest_error_report_url}", "failed"
|
150
183
|
)
|
151
|
-
|
152
|
-
|
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,
|
153
188
|
)
|
154
189
|
else:
|
155
|
-
|
156
|
-
|
157
|
-
f"See {expected_parrot_url}/home/remote-inference for
|
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,
|
158
194
|
)
|
159
|
-
|
195
|
+
|
196
|
+
results_uuid = remote_job_data.get("results_uuid")
|
197
|
+
if results_uuid:
|
198
|
+
self.logger.add_info("results_uuid", results_uuid)
|
199
|
+
results = object_fetcher(
|
200
|
+
results_uuid, expected_object_type="results"
|
201
|
+
)
|
202
|
+
results.job_uuid = job_uuid
|
203
|
+
results.results_uuid = results_uuid
|
204
|
+
return results
|
205
|
+
else:
|
206
|
+
return None
|
207
|
+
|
160
208
|
elif status == "completed":
|
161
209
|
results_uuid = remote_job_data.get("results_uuid")
|
210
|
+
self.logger.add_info("results_uuid", results_uuid)
|
162
211
|
results_url = remote_job_data.get("results_url")
|
212
|
+
self.logger.add_info("results_url", results_url)
|
163
213
|
results = object_fetcher(results_uuid, expected_object_type="results")
|
164
|
-
|
165
|
-
|
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
|
166
220
|
return results
|
221
|
+
|
167
222
|
else:
|
168
|
-
duration = poll_interval
|
169
223
|
time_checked = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
f"\r{frames[i % len(frames)]} Job status: {status} - last update: {time_checked}",
|
176
|
-
end="",
|
177
|
-
flush=True,
|
178
|
-
)
|
179
|
-
time.sleep(0.1)
|
180
|
-
i += 1
|
224
|
+
self.logger.update(
|
225
|
+
f"Job status: {status} - last update: {time_checked}",
|
226
|
+
status=JobsStatus.RUNNING,
|
227
|
+
)
|
228
|
+
time.sleep(poll_interval)
|
181
229
|
|
182
230
|
def use_remote_inference(self, disable_remote_inference: bool) -> bool:
|
231
|
+
import requests
|
232
|
+
|
183
233
|
if disable_remote_inference:
|
184
234
|
return False
|
185
235
|
if not disable_remote_inference:
|
186
236
|
try:
|
187
|
-
from edsl import Coop
|
237
|
+
from edsl.coop.coop import Coop
|
188
238
|
|
189
239
|
user_edsl_settings = Coop().edsl_settings
|
190
240
|
return user_edsl_settings.get("remote_inference", False)
|
@@ -199,10 +249,8 @@ class JobsRemoteInferenceHandler:
|
|
199
249
|
self,
|
200
250
|
iterations: int = 1,
|
201
251
|
remote_inference_description: Optional[str] = None,
|
202
|
-
remote_inference_results_visibility: Optional[
|
203
|
-
|
204
|
-
] = "unlisted",
|
205
|
-
) -> Union[Results, None]:
|
252
|
+
remote_inference_results_visibility: Optional[VisibilityType] = "unlisted",
|
253
|
+
) -> Union["Results", None]:
|
206
254
|
"""
|
207
255
|
Creates and polls a remote inference job asynchronously.
|
208
256
|
Reuses existing synchronous methods but runs them in an async context.
|