edsl 0.1.27.dev2__py3-none-any.whl → 0.1.29__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 +107 -30
- edsl/BaseDiff.py +260 -0
- edsl/__init__.py +25 -21
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +103 -46
- edsl/agents/AgentList.py +97 -13
- edsl/agents/Invigilator.py +23 -10
- edsl/agents/InvigilatorBase.py +19 -14
- edsl/agents/PromptConstructionMixin.py +342 -100
- edsl/agents/descriptors.py +5 -2
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conjure/AgentConstructionMixin.py +152 -0
- edsl/conjure/Conjure.py +56 -0
- edsl/conjure/InputData.py +659 -0
- edsl/conjure/InputDataCSV.py +48 -0
- edsl/conjure/InputDataMixinQuestionStats.py +182 -0
- edsl/conjure/InputDataPyRead.py +91 -0
- edsl/conjure/InputDataSPSS.py +8 -0
- edsl/conjure/InputDataStata.py +8 -0
- edsl/conjure/QuestionOptionMixin.py +76 -0
- edsl/conjure/QuestionTypeMixin.py +23 -0
- edsl/conjure/RawQuestion.py +65 -0
- edsl/conjure/SurveyResponses.py +7 -0
- edsl/conjure/__init__.py +9 -4
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/naming_utilities.py +263 -0
- edsl/conjure/utilities.py +165 -28
- edsl/conversation/Conversation.py +238 -0
- edsl/conversation/car_buying.py +58 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/coop.py +337 -121
- edsl/coop/utils.py +56 -70
- edsl/data/Cache.py +74 -22
- edsl/data/CacheHandler.py +10 -9
- edsl/data/SQLiteDict.py +11 -3
- edsl/inference_services/AnthropicService.py +1 -0
- edsl/inference_services/DeepInfraService.py +20 -13
- edsl/inference_services/GoogleService.py +7 -1
- edsl/inference_services/InferenceServicesCollection.py +33 -7
- edsl/inference_services/OpenAIService.py +17 -10
- edsl/inference_services/models_available_cache.py +69 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +322 -73
- edsl/jobs/buckets/BucketCollection.py +9 -3
- edsl/jobs/buckets/ModelBuckets.py +4 -2
- edsl/jobs/buckets/TokenBucket.py +1 -2
- edsl/jobs/interviews/Interview.py +7 -10
- edsl/jobs/interviews/InterviewStatusMixin.py +3 -3
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +39 -20
- edsl/jobs/interviews/retry_management.py +4 -4
- edsl/jobs/runners/JobsRunnerAsyncio.py +103 -65
- edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
- edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +42 -55
- edsl/language_models/ModelList.py +96 -0
- edsl/language_models/registry.py +14 -0
- edsl/language_models/repair.py +97 -25
- edsl/notebooks/Notebook.py +157 -32
- edsl/prompts/Prompt.py +31 -19
- edsl/questions/QuestionBase.py +145 -23
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +3 -3
- edsl/questions/QuestionFunctional.py +0 -3
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +16 -8
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +9 -4
- edsl/questions/question_registry.py +27 -31
- edsl/questions/settings.py +1 -1
- edsl/results/Dataset.py +31 -0
- edsl/results/DatasetExportMixin.py +493 -0
- edsl/results/Result.py +42 -82
- edsl/results/Results.py +178 -66
- edsl/results/ResultsDBMixin.py +10 -9
- edsl/results/ResultsExportMixin.py +23 -507
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +9 -9
- edsl/scenarios/FileStore.py +140 -0
- edsl/scenarios/Scenario.py +59 -6
- edsl/scenarios/ScenarioList.py +138 -52
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +73 -0
- edsl/study/Study.py +498 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +124 -37
- edsl/surveys/SurveyExportMixin.py +25 -5
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/tools/plotting.py +4 -2
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/interface.py +90 -73
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/utilities.py +59 -6
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/METADATA +42 -15
- edsl-0.1.29.dist-info/RECORD +203 -0
- edsl/conjure/RawResponseColumn.py +0 -327
- edsl/conjure/SurveyBuilder.py +0 -308
- edsl/conjure/SurveyBuilderCSV.py +0 -78
- edsl/conjure/SurveyBuilderSPSS.py +0 -118
- edsl/data/RemoteDict.py +0 -103
- edsl-0.1.27.dev2.dist-info/RECORD +0 -172
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/LICENSE +0 -0
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.29.dist-info}/WHEEL +0 -0
@@ -6,15 +6,9 @@ import asyncio
|
|
6
6
|
import time
|
7
7
|
from typing import Any, Type, List, Generator, Optional
|
8
8
|
|
9
|
-
from edsl.agents import Agent
|
10
|
-
from edsl.language_models import LanguageModel
|
11
|
-
from edsl.scenarios import Scenario
|
12
|
-
from edsl.surveys import Survey
|
13
|
-
|
14
9
|
from edsl.jobs.Answers import Answers
|
15
10
|
from edsl.surveys.base import EndOfSurvey
|
16
11
|
from edsl.jobs.buckets.ModelBuckets import ModelBuckets
|
17
|
-
|
18
12
|
from edsl.jobs.tasks.TaskCreators import TaskCreators
|
19
13
|
|
20
14
|
from edsl.jobs.interviews.InterviewStatusLog import InterviewStatusLog
|
@@ -60,9 +54,9 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
|
|
60
54
|
self.debug = debug
|
61
55
|
self.iteration = iteration
|
62
56
|
self.cache = cache
|
63
|
-
self.answers: dict[
|
64
|
-
|
65
|
-
|
57
|
+
self.answers: dict[str, str] = (
|
58
|
+
Answers()
|
59
|
+
) # will get filled in as interview progresses
|
66
60
|
self.sidecar_model = sidecar_model
|
67
61
|
|
68
62
|
# Trackers
|
@@ -101,9 +95,12 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
|
|
101
95
|
self.sidecar_model = sidecar_model
|
102
96
|
|
103
97
|
# if no model bucket is passed, create an 'infinity' bucket with no rate limits
|
104
|
-
|
98
|
+
# print("model_buckets", model_buckets)
|
99
|
+
if model_buckets is None or hasattr(self.agent, "answer_question_directly"):
|
105
100
|
model_buckets = ModelBuckets.infinity_bucket()
|
106
101
|
|
102
|
+
# model_buckets = ModelBuckets.infinity_bucket()
|
103
|
+
|
107
104
|
## build the tasks using the InterviewTaskBuildingMixin
|
108
105
|
## This is the key part---it creates a task for each question,
|
109
106
|
## with dependencies on the questions that must be answered before this one can be answered.
|
@@ -17,9 +17,9 @@ class InterviewStatusMixin:
|
|
17
17
|
The keys are the question names; the values are the lists of status log changes for each task.
|
18
18
|
"""
|
19
19
|
for task_creator in self.task_creators.values():
|
20
|
-
self._task_status_log_dict[
|
21
|
-
task_creator.
|
22
|
-
|
20
|
+
self._task_status_log_dict[task_creator.question.question_name] = (
|
21
|
+
task_creator.status_log
|
22
|
+
)
|
23
23
|
return self._task_status_log_dict
|
24
24
|
|
25
25
|
@property
|
@@ -5,17 +5,19 @@ import asyncio
|
|
5
5
|
import time
|
6
6
|
import traceback
|
7
7
|
from typing import Generator, Union
|
8
|
+
|
8
9
|
from edsl import CONFIG
|
9
10
|
from edsl.exceptions import InterviewTimeoutError
|
10
|
-
|
11
|
-
from edsl.questions.QuestionBase import QuestionBase
|
11
|
+
|
12
|
+
# from edsl.questions.QuestionBase import QuestionBase
|
12
13
|
from edsl.surveys.base import EndOfSurvey
|
13
14
|
from edsl.jobs.buckets.ModelBuckets import ModelBuckets
|
14
15
|
from edsl.jobs.interviews.interview_exception_tracking import InterviewExceptionEntry
|
15
16
|
from edsl.jobs.interviews.retry_management import retry_strategy
|
16
17
|
from edsl.jobs.tasks.task_status_enum import TaskStatus
|
17
18
|
from edsl.jobs.tasks.QuestionTaskCreator import QuestionTaskCreator
|
18
|
-
|
19
|
+
|
20
|
+
# from edsl.agents.InvigilatorBase import InvigilatorBase
|
19
21
|
|
20
22
|
TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
|
21
23
|
|
@@ -44,6 +46,7 @@ class InterviewTaskBuildingMixin:
|
|
44
46
|
scenario=self.scenario,
|
45
47
|
model=self.model,
|
46
48
|
debug=debug,
|
49
|
+
survey=self.survey,
|
47
50
|
memory_plan=self.survey.memory_plan,
|
48
51
|
current_answers=self.answers,
|
49
52
|
iteration=self.iteration,
|
@@ -149,29 +152,45 @@ class InterviewTaskBuildingMixin:
|
|
149
152
|
async def _answer_question_and_record_task(
|
150
153
|
self,
|
151
154
|
*,
|
152
|
-
question: QuestionBase,
|
155
|
+
question: "QuestionBase",
|
153
156
|
debug: bool,
|
154
157
|
task=None,
|
155
|
-
) -> AgentResponseDict:
|
158
|
+
) -> "AgentResponseDict":
|
156
159
|
"""Answer a question and records the task.
|
157
160
|
|
158
161
|
This in turn calls the the passed-in agent's async_answer_question method, which returns a response dictionary.
|
159
162
|
Note that is updates answers dictionary with the response.
|
160
163
|
"""
|
161
|
-
|
164
|
+
from edsl.data_transfer_models import AgentResponseDict
|
165
|
+
|
166
|
+
try:
|
167
|
+
invigilator = self._get_invigilator(question, debug=debug)
|
162
168
|
|
163
|
-
|
164
|
-
|
169
|
+
if self._skip_this_question(question):
|
170
|
+
return invigilator.get_failed_task_result()
|
165
171
|
|
166
|
-
|
167
|
-
|
168
|
-
|
172
|
+
response: AgentResponseDict = await self._attempt_to_answer_question(
|
173
|
+
invigilator, task
|
174
|
+
)
|
169
175
|
|
170
|
-
|
176
|
+
self._add_answer(response=response, question=question)
|
171
177
|
|
172
|
-
|
173
|
-
|
174
|
-
|
178
|
+
# With the answer to the question, we can now cancel any skipped questions
|
179
|
+
self._cancel_skipped_questions(question)
|
180
|
+
return AgentResponseDict(**response)
|
181
|
+
except Exception as e:
|
182
|
+
raise e
|
183
|
+
# import traceback
|
184
|
+
# print("Exception caught:")
|
185
|
+
# traceback.print_exc()
|
186
|
+
|
187
|
+
# # Extract and print the traceback info
|
188
|
+
# tb = e.__traceback__
|
189
|
+
# while tb is not None:
|
190
|
+
# print(f"File {tb.tb_frame.f_code.co_filename}, line {tb.tb_lineno}, in {tb.tb_frame.f_code.co_name}")
|
191
|
+
# tb = tb.tb_next
|
192
|
+
# breakpoint()
|
193
|
+
# raise e
|
175
194
|
|
176
195
|
def _add_answer(self, response: AgentResponseDict, question: QuestionBase) -> None:
|
177
196
|
"""Add the answer to the answers dictionary.
|
@@ -239,11 +258,11 @@ class InterviewTaskBuildingMixin:
|
|
239
258
|
"""
|
240
259
|
current_question_index: int = self.to_index[current_question.question_name]
|
241
260
|
|
242
|
-
next_question: Union[
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
261
|
+
next_question: Union[int, EndOfSurvey] = (
|
262
|
+
self.survey.rule_collection.next_question(
|
263
|
+
q_now=current_question_index,
|
264
|
+
answers=self.answers | self.scenario | self.agent["traits"],
|
265
|
+
)
|
247
266
|
)
|
248
267
|
|
249
268
|
next_question_index = next_question.next_q
|
@@ -13,17 +13,17 @@ EDSL_MAX_BACKOFF_SEC = float(CONFIG.get("EDSL_MAX_BACKOFF_SEC"))
|
|
13
13
|
EDSL_MAX_ATTEMPTS = int(CONFIG.get("EDSL_MAX_ATTEMPTS"))
|
14
14
|
|
15
15
|
|
16
|
-
def print_retry(retry_state, print_to_terminal=
|
16
|
+
def print_retry(retry_state, print_to_terminal=True):
|
17
17
|
"Prints details on tenacity retries."
|
18
18
|
attempt_number = retry_state.attempt_number
|
19
19
|
exception = retry_state.outcome.exception()
|
20
20
|
wait_time = retry_state.next_action.sleep
|
21
|
-
# breakpoint()
|
22
21
|
if print_to_terminal:
|
23
22
|
print(
|
24
|
-
f"Attempt {attempt_number} failed with exception: {
|
23
|
+
f"Attempt {attempt_number} failed with exception:" f"{exception}",
|
25
24
|
f"now waiting {wait_time:.2f} seconds before retrying."
|
26
|
-
f"
|
25
|
+
f"Parameters: start={EDSL_BACKOFF_START_SEC}, max={EDSL_MAX_BACKOFF_SEC}, max_attempts={EDSL_MAX_ATTEMPTS}."
|
26
|
+
"\n\n",
|
27
27
|
)
|
28
28
|
|
29
29
|
|
@@ -1,26 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import time
|
3
3
|
import asyncio
|
4
|
-
import
|
4
|
+
import time
|
5
5
|
from contextlib import contextmanager
|
6
6
|
|
7
7
|
from typing import Coroutine, List, AsyncGenerator, Optional, Union
|
8
8
|
|
9
|
-
from rich.live import Live
|
10
|
-
from rich.console import Console
|
11
|
-
|
12
9
|
from edsl import shared_globals
|
13
|
-
from edsl.results import Results, Result
|
14
|
-
|
15
10
|
from edsl.jobs.interviews.Interview import Interview
|
16
|
-
from edsl.utilities.decorators import jupyter_nb_handler
|
17
|
-
from edsl.jobs.Jobs import Jobs
|
18
11
|
from edsl.jobs.runners.JobsRunnerStatusMixin import JobsRunnerStatusMixin
|
19
|
-
from edsl.language_models import LanguageModel
|
20
|
-
from edsl.data.Cache import Cache
|
21
|
-
|
22
12
|
from edsl.jobs.tasks.TaskHistory import TaskHistory
|
23
13
|
from edsl.jobs.buckets.BucketCollection import BucketCollection
|
14
|
+
from edsl.utilities.decorators import jupyter_nb_handler
|
24
15
|
|
25
16
|
|
26
17
|
class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
@@ -32,20 +23,20 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
32
23
|
|
33
24
|
def __init__(self, jobs: Jobs):
|
34
25
|
self.jobs = jobs
|
35
|
-
|
26
|
+
# this creates the interviews, which can take a while
|
36
27
|
self.interviews: List["Interview"] = jobs.interviews()
|
37
28
|
self.bucket_collection: "BucketCollection" = jobs.bucket_collection
|
38
29
|
self.total_interviews: List["Interview"] = []
|
39
30
|
|
40
|
-
async def
|
31
|
+
async def run_async_generator(
|
41
32
|
self,
|
42
|
-
cache: Cache,
|
33
|
+
cache: "Cache",
|
43
34
|
n: int = 1,
|
44
35
|
debug: bool = False,
|
45
36
|
stop_on_exception: bool = False,
|
46
37
|
sidecar_model: "LanguageModel" = None,
|
47
38
|
total_interviews: Optional[List["Interview"]] = None,
|
48
|
-
) -> AsyncGenerator[Result, None]:
|
39
|
+
) -> AsyncGenerator["Result", None]:
|
49
40
|
"""Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
50
41
|
|
51
42
|
Completed tasks are yielded as they are completed.
|
@@ -96,6 +87,20 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
96
87
|
) # set the cache for the first interview
|
97
88
|
self.total_interviews.append(interview)
|
98
89
|
|
90
|
+
async def run_async(self, cache=None) -> Results:
|
91
|
+
if cache is None:
|
92
|
+
self.cache = Cache()
|
93
|
+
else:
|
94
|
+
self.cache = cache
|
95
|
+
data = []
|
96
|
+
async for result in self.run_async_generator(cache=self.cache):
|
97
|
+
data.append(result)
|
98
|
+
return Results(survey=self.jobs.survey, data=data)
|
99
|
+
|
100
|
+
def simple_run(self):
|
101
|
+
data = asyncio.run(self.run_async())
|
102
|
+
return Results(survey=self.jobs.survey, data=data)
|
103
|
+
|
99
104
|
async def _build_interview_task(
|
100
105
|
self,
|
101
106
|
*,
|
@@ -138,19 +143,21 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
138
143
|
|
139
144
|
prompt_dictionary = {}
|
140
145
|
for answer_key_name in answer_key_names:
|
141
|
-
prompt_dictionary[
|
142
|
-
answer_key_name
|
143
|
-
|
144
|
-
prompt_dictionary[
|
145
|
-
answer_key_name
|
146
|
-
|
146
|
+
prompt_dictionary[answer_key_name + "_user_prompt"] = (
|
147
|
+
question_name_to_prompts[answer_key_name]["user_prompt"]
|
148
|
+
)
|
149
|
+
prompt_dictionary[answer_key_name + "_system_prompt"] = (
|
150
|
+
question_name_to_prompts[answer_key_name]["system_prompt"]
|
151
|
+
)
|
147
152
|
|
148
153
|
raw_model_results_dictionary = {}
|
149
154
|
for result in valid_results:
|
150
155
|
question_name = result["question_name"]
|
151
|
-
raw_model_results_dictionary[
|
152
|
-
|
153
|
-
|
156
|
+
raw_model_results_dictionary[question_name + "_raw_model_response"] = (
|
157
|
+
result["raw_model_response"]
|
158
|
+
)
|
159
|
+
|
160
|
+
from edsl.results.Result import Result
|
154
161
|
|
155
162
|
result = Result(
|
156
163
|
agent=interview.agent,
|
@@ -180,6 +187,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
180
187
|
print_exceptions: bool = True,
|
181
188
|
) -> "Coroutine":
|
182
189
|
"""Runs a collection of interviews, handling both async and sync contexts."""
|
190
|
+
from rich.console import Console
|
191
|
+
|
183
192
|
console = Console()
|
184
193
|
self.results = []
|
185
194
|
self.start_time = time.monotonic()
|
@@ -187,39 +196,15 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
187
196
|
self.cache = cache
|
188
197
|
self.sidecar_model = sidecar_model
|
189
198
|
|
190
|
-
|
191
|
-
return self.status_table(self.results, self.elapsed_time)
|
199
|
+
from edsl.results.Results import Results
|
192
200
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
yield DummyLive()
|
197
|
-
|
198
|
-
class DummyLive:
|
199
|
-
def update(self, *args, **kwargs):
|
200
|
-
"""A dummy update method that does nothing."""
|
201
|
-
pass
|
202
|
-
|
203
|
-
progress_bar_context = (
|
204
|
-
Live(generate_table(), console=console, refresh_per_second=5)
|
205
|
-
if progress_bar
|
206
|
-
else no_op_cm()
|
207
|
-
)
|
208
|
-
|
209
|
-
with cache as c:
|
210
|
-
with progress_bar_context as live:
|
211
|
-
|
212
|
-
async def update_progress_bar():
|
213
|
-
"""Updates the progress bar at fixed intervals."""
|
214
|
-
while True:
|
215
|
-
live.update(generate_table())
|
216
|
-
await asyncio.sleep(0.1) # Update interval
|
217
|
-
if self.completed:
|
218
|
-
break
|
201
|
+
if not progress_bar:
|
202
|
+
# print("Running without progress bar")
|
203
|
+
with cache as c:
|
219
204
|
|
220
205
|
async def process_results():
|
221
206
|
"""Processes results from interviews."""
|
222
|
-
async for result in self.
|
207
|
+
async for result in self.run_async_generator(
|
223
208
|
n=n,
|
224
209
|
debug=debug,
|
225
210
|
stop_on_exception=stop_on_exception,
|
@@ -227,25 +212,76 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
227
212
|
sidecar_model=sidecar_model,
|
228
213
|
):
|
229
214
|
self.results.append(result)
|
230
|
-
live.update(generate_table())
|
231
215
|
self.completed = True
|
232
216
|
|
233
|
-
|
217
|
+
await asyncio.gather(process_results())
|
234
218
|
|
235
|
-
|
236
|
-
|
237
|
-
|
219
|
+
results = Results(survey=self.jobs.survey, data=self.results)
|
220
|
+
else:
|
221
|
+
# print("Running with progress bar")
|
222
|
+
from rich.live import Live
|
223
|
+
from rich.console import Console
|
224
|
+
|
225
|
+
def generate_table():
|
226
|
+
return self.status_table(self.results, self.elapsed_time)
|
227
|
+
|
228
|
+
@contextmanager
|
229
|
+
def no_op_cm():
|
230
|
+
"""A no-op context manager with a dummy update method."""
|
231
|
+
yield DummyLive()
|
232
|
+
|
233
|
+
class DummyLive:
|
234
|
+
def update(self, *args, **kwargs):
|
235
|
+
"""A dummy update method that does nothing."""
|
238
236
|
pass
|
239
|
-
finally:
|
240
|
-
progress_task.cancel() # Cancel the progress_task when process_results is done
|
241
|
-
await progress_task
|
242
237
|
|
243
|
-
|
238
|
+
progress_bar_context = (
|
239
|
+
Live(generate_table(), console=console, refresh_per_second=5)
|
240
|
+
if progress_bar
|
241
|
+
else no_op_cm()
|
242
|
+
)
|
244
243
|
|
245
|
-
|
246
|
-
|
244
|
+
with cache as c:
|
245
|
+
with progress_bar_context as live:
|
246
|
+
|
247
|
+
async def update_progress_bar():
|
248
|
+
"""Updates the progress bar at fixed intervals."""
|
249
|
+
while True:
|
250
|
+
live.update(generate_table())
|
251
|
+
await asyncio.sleep(0.00001) # Update interval
|
252
|
+
if self.completed:
|
253
|
+
break
|
254
|
+
|
255
|
+
async def process_results():
|
256
|
+
"""Processes results from interviews."""
|
257
|
+
async for result in self.run_async_generator(
|
258
|
+
n=n,
|
259
|
+
debug=debug,
|
260
|
+
stop_on_exception=stop_on_exception,
|
261
|
+
cache=c,
|
262
|
+
sidecar_model=sidecar_model,
|
263
|
+
):
|
264
|
+
self.results.append(result)
|
265
|
+
live.update(generate_table())
|
266
|
+
self.completed = True
|
267
|
+
|
268
|
+
progress_task = asyncio.create_task(update_progress_bar())
|
269
|
+
|
270
|
+
try:
|
271
|
+
await asyncio.gather(process_results(), progress_task)
|
272
|
+
except asyncio.CancelledError:
|
273
|
+
pass
|
274
|
+
finally:
|
275
|
+
progress_task.cancel() # Cancel the progress_task when process_results is done
|
276
|
+
await progress_task
|
277
|
+
|
278
|
+
await asyncio.sleep(1) # short delay to show the final status
|
279
|
+
|
280
|
+
# one more update
|
281
|
+
live.update(generate_table())
|
282
|
+
|
283
|
+
results = Results(survey=self.jobs.survey, data=self.results)
|
247
284
|
|
248
|
-
results = Results(survey=self.jobs.survey, data=self.results)
|
249
285
|
task_history = TaskHistory(self.total_interviews, include_traceback=False)
|
250
286
|
results.task_history = task_history
|
251
287
|
|
@@ -259,6 +295,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
259
295
|
for interview in self.total_interviews
|
260
296
|
if interview.has_exceptions
|
261
297
|
]
|
298
|
+
from edsl.jobs.Jobs import Jobs
|
299
|
+
|
262
300
|
results.failed_jobs = Jobs.from_interviews(
|
263
301
|
[interview for interview in failed_interviews]
|
264
302
|
)
|
@@ -81,7 +81,7 @@ class JobsRunnerStatusData:
|
|
81
81
|
>>> completed_tasks = []
|
82
82
|
>>> elapsed_time = 0
|
83
83
|
>>> JobsRunnerStatusData().generate_status_summary(completed_tasks, elapsed_time, interviews)
|
84
|
-
{'Elapsed time': '0.0 sec.', 'Total interviews requested': '1 ', 'Completed interviews': '0 ', 'Percent complete': '0 %', 'Average time per interview': 'NA', 'Task remaining': '1 ', 'Estimated time remaining': 'NA', 'model_queues': [{'model_name': '
|
84
|
+
{'Elapsed time': '0.0 sec.', 'Total interviews requested': '1 ', 'Completed interviews': '0 ', 'Percent complete': '0 %', 'Average time per interview': 'NA', 'Task remaining': '1 ', 'Estimated time remaining': 'NA', 'model_queues': [{'model_name': '...', 'TPM_limit_k': ..., 'RPM_limit_k': ..., 'num_tasks_waiting': 0, 'token_usage_info': [{'cache_status': 'new_token_usage', 'details': [{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], 'cost': '$0.00000'}, {'cache_status': 'cached_token_usage', 'details': [{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], 'cost': '$0.00000'}]}]}
|
85
85
|
"""
|
86
86
|
|
87
87
|
models_to_tokens = defaultdict(InterviewTokenUsage)
|
@@ -176,7 +176,7 @@ class JobsRunnerStatusData:
|
|
176
176
|
>>> model = interviews[0].model
|
177
177
|
>>> num_waiting = 0
|
178
178
|
>>> JobsRunnerStatusData()._get_model_info(model, num_waiting, models_to_tokens)
|
179
|
-
{'model_name': 'gpt-4-1106-preview', 'TPM_limit_k':
|
179
|
+
{'model_name': 'gpt-4-1106-preview', 'TPM_limit_k': ..., 'RPM_limit_k': ..., 'num_tasks_waiting': 0, 'token_usage_info': [{'cache_status': 'new_token_usage', 'details': [{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], 'cost': '$0.00000'}, {'cache_status': 'cached_token_usage', 'details': [{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], 'cost': '$0.00000'}]}
|
180
180
|
"""
|
181
181
|
|
182
182
|
prices = get_token_pricing(model.model)
|
@@ -234,4 +234,4 @@ class JobsRunnerStatusData:
|
|
234
234
|
if __name__ == "__main__":
|
235
235
|
import doctest
|
236
236
|
|
237
|
-
doctest.testmod()
|
237
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
from typing import Callable, Union, List
|
3
3
|
from collections import UserList, UserDict
|
4
|
-
|
4
|
+
import time
|
5
5
|
|
6
6
|
from edsl.jobs.buckets import ModelBuckets
|
7
7
|
from edsl.exceptions import InterviewErrorPriorTaskCanceled
|
@@ -132,7 +132,7 @@ class QuestionTaskCreator(UserList):
|
|
132
132
|
self.waiting = True
|
133
133
|
self.task_status = TaskStatus.WAITING_FOR_REQUEST_CAPACITY
|
134
134
|
|
135
|
-
await self.
|
135
|
+
await self.tokens_bucket.get_tokens(1)
|
136
136
|
|
137
137
|
self.task_status = TaskStatus.API_CALL_IN_PROGRESS
|
138
138
|
try:
|
@@ -151,6 +151,8 @@ class QuestionTaskCreator(UserList):
|
|
151
151
|
self.requests_bucket.add_tokens(1)
|
152
152
|
self.from_cache = True
|
153
153
|
|
154
|
+
_ = results.pop("cached_response", None)
|
155
|
+
|
154
156
|
tracker = self.cached_token_usage if self.from_cache else self.new_token_usage
|
155
157
|
|
156
158
|
# TODO: This is hacky. The 'func' call should return an object that definitely has a 'usage' key.
|
edsl/jobs/tasks/TaskHistory.py
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
from edsl.jobs.tasks.task_status_enum import TaskStatus
|
2
|
-
from matplotlib import pyplot as plt
|
3
2
|
from typing import List, Optional
|
4
|
-
|
5
|
-
import matplotlib.pyplot as plt
|
6
3
|
from io import BytesIO
|
7
4
|
import base64
|
8
5
|
|
@@ -75,6 +72,8 @@ class TaskHistory:
|
|
75
72
|
|
76
73
|
def plot_completion_times(self):
|
77
74
|
"""Plot the completion times for each task."""
|
75
|
+
import matplotlib.pyplot as plt
|
76
|
+
|
78
77
|
updates = self.get_updates()
|
79
78
|
|
80
79
|
elapsed = [update.max_time - update.min_time for update in updates]
|
@@ -126,6 +125,8 @@ class TaskHistory:
|
|
126
125
|
rows = int(len(TaskStatus) ** 0.5) + 1
|
127
126
|
cols = (len(TaskStatus) + rows - 1) // rows # Ensure all plots fit
|
128
127
|
|
128
|
+
import matplotlib.pyplot as plt
|
129
|
+
|
129
130
|
fig, axes = plt.subplots(rows, cols, figsize=(15, 10))
|
130
131
|
axes = axes.flatten() # Flatten in case of a single row/column
|
131
132
|
|