edsl 0.1.33.dev2__py3-none-any.whl → 0.1.33.dev3__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 +9 -3
- edsl/__init__.py +1 -0
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +6 -6
- edsl/agents/Invigilator.py +6 -3
- edsl/agents/InvigilatorBase.py +8 -27
- edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +101 -29
- edsl/config.py +26 -34
- edsl/coop/coop.py +11 -2
- edsl/data_transfer_models.py +27 -73
- edsl/enums.py +2 -0
- edsl/inference_services/GoogleService.py +1 -1
- edsl/inference_services/InferenceServiceABC.py +44 -13
- edsl/inference_services/OpenAIService.py +7 -4
- edsl/inference_services/TestService.py +24 -15
- edsl/inference_services/TogetherAIService.py +170 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/Jobs.py +18 -8
- edsl/jobs/buckets/BucketCollection.py +24 -15
- edsl/jobs/buckets/TokenBucket.py +64 -10
- edsl/jobs/interviews/Interview.py +115 -47
- edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +16 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +2 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +86 -161
- edsl/jobs/runners/JobsRunnerStatus.py +331 -0
- edsl/jobs/tasks/TaskHistory.py +17 -0
- edsl/language_models/LanguageModel.py +26 -31
- edsl/language_models/registry.py +13 -9
- edsl/questions/QuestionBase.py +64 -16
- edsl/questions/QuestionBudget.py +93 -41
- edsl/questions/QuestionFreeText.py +6 -0
- edsl/questions/QuestionMultipleChoice.py +11 -26
- edsl/questions/QuestionNumerical.py +5 -4
- edsl/questions/Quick.py +41 -0
- edsl/questions/ResponseValidatorABC.py +6 -5
- edsl/questions/derived/QuestionLinearScale.py +4 -1
- edsl/questions/derived/QuestionTopK.py +4 -1
- edsl/questions/derived/QuestionYesNo.py +8 -2
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/results/DatasetExportMixin.py +5 -1
- edsl/results/Result.py +1 -1
- edsl/results/Results.py +4 -1
- edsl/scenarios/FileStore.py +71 -10
- edsl/scenarios/Scenario.py +86 -21
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +13 -0
- edsl/scenarios/ScenarioListPdfMixin.py +150 -4
- edsl/study/Study.py +32 -0
- edsl/surveys/Rule.py +10 -1
- edsl/surveys/RuleCollection.py +19 -3
- edsl/surveys/Survey.py +7 -0
- edsl/templates/error_reporting/interview_details.html +6 -1
- edsl/utilities/utilities.py +9 -1
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.33.dev3.dist-info}/METADATA +2 -1
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.33.dev3.dist-info}/RECORD +61 -55
- edsl/jobs/interviews/retry_management.py +0 -39
- edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.33.dev3.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dev2.dist-info → edsl-0.1.33.dev3.dist-info}/WHEEL +0 -0
@@ -1,333 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
from typing import List, DefaultDict
|
3
|
-
import asyncio
|
4
|
-
from typing import Type
|
5
|
-
from collections import defaultdict
|
6
|
-
|
7
|
-
from typing import Literal, List, Type, DefaultDict
|
8
|
-
from collections import UserDict, defaultdict
|
9
|
-
|
10
|
-
from edsl.jobs.interviews.InterviewStatusDictionary import InterviewStatusDictionary
|
11
|
-
from edsl.jobs.tokens.InterviewTokenUsage import InterviewTokenUsage
|
12
|
-
from edsl.jobs.tokens.TokenUsage import TokenUsage
|
13
|
-
from edsl.enums import get_token_pricing
|
14
|
-
from edsl.jobs.tasks.task_status_enum import TaskStatus
|
15
|
-
|
16
|
-
InterviewTokenUsageMapping = DefaultDict[str, InterviewTokenUsage]
|
17
|
-
|
18
|
-
from edsl.jobs.interviews.InterviewStatistic import InterviewStatistic
|
19
|
-
from edsl.jobs.interviews.InterviewStatisticsCollection import (
|
20
|
-
InterviewStatisticsCollection,
|
21
|
-
)
|
22
|
-
from edsl.jobs.tokens.InterviewTokenUsage import InterviewTokenUsage
|
23
|
-
|
24
|
-
|
25
|
-
# return {"cache_status": token_usage_type, "details": details, "cost": f"${token_usage.cost(prices):.5f}"}
|
26
|
-
|
27
|
-
from dataclasses import dataclass, asdict
|
28
|
-
|
29
|
-
from rich.text import Text
|
30
|
-
from rich.box import SIMPLE
|
31
|
-
from rich.table import Table
|
32
|
-
|
33
|
-
|
34
|
-
@dataclass
|
35
|
-
class ModelInfo:
|
36
|
-
model_name: str
|
37
|
-
TPM_limit_k: float
|
38
|
-
RPM_limit_k: float
|
39
|
-
num_tasks_waiting: int
|
40
|
-
token_usage_info: dict
|
41
|
-
|
42
|
-
|
43
|
-
@dataclass
|
44
|
-
class ModelTokenUsageStats:
|
45
|
-
token_usage_type: str
|
46
|
-
details: List[dict]
|
47
|
-
cost: str
|
48
|
-
|
49
|
-
|
50
|
-
class Stats:
|
51
|
-
def elapsed_time(self):
|
52
|
-
InterviewStatistic("elapsed_time", value=elapsed_time, digits=1, units="sec.")
|
53
|
-
|
54
|
-
|
55
|
-
class JobsRunnerStatusMixin:
|
56
|
-
# @staticmethod
|
57
|
-
# def status_dict(interviews: List[Type["Interview"]]) -> List[Type[InterviewStatusDictionary]]:
|
58
|
-
# """
|
59
|
-
# >>> from edsl.jobs.interviews.Interview import Interview
|
60
|
-
# >>> interviews = [Interview.example()]
|
61
|
-
# >>> JobsRunnerStatusMixin().status_dict(interviews)
|
62
|
-
# [InterviewStatusDictionary({<TaskStatus.NOT_STARTED: 1>: 0, <TaskStatus.WAITING_FOR_DEPENDENCIES: 2>: 0, <TaskStatus.CANCELLED: 3>: 0, <TaskStatus.PARENT_FAILED: 4>: 0, <TaskStatus.WAITING_FOR_REQUEST_CAPACITY: 5>: 0, <TaskStatus.WAITING_FOR_TOKEN_CAPACITY: 6>: 0, <TaskStatus.API_CALL_IN_PROGRESS: 7>: 0, <TaskStatus.SUCCESS: 8>: 0, <TaskStatus.FAILED: 9>: 0, 'number_from_cache': 0})]
|
63
|
-
# """
|
64
|
-
# return [interview.interview_status for interview in interviews]
|
65
|
-
|
66
|
-
def _compute_statistic(stat_name: str, completed_tasks, elapsed_time, interviews):
|
67
|
-
stat_definitions = {
|
68
|
-
"elapsed_time": lambda: InterviewStatistic(
|
69
|
-
"elapsed_time", value=elapsed_time, digits=1, units="sec."
|
70
|
-
),
|
71
|
-
"total_interviews_requested": lambda: InterviewStatistic(
|
72
|
-
"total_interviews_requested", value=len(interviews), units=""
|
73
|
-
),
|
74
|
-
"completed_interviews": lambda: InterviewStatistic(
|
75
|
-
"completed_interviews", value=len(completed_tasks), units=""
|
76
|
-
),
|
77
|
-
"percent_complete": lambda: InterviewStatistic(
|
78
|
-
"percent_complete",
|
79
|
-
value=(
|
80
|
-
len(completed_tasks) / len(interviews) * 100
|
81
|
-
if len(interviews) > 0
|
82
|
-
else "NA"
|
83
|
-
),
|
84
|
-
digits=0,
|
85
|
-
units="%",
|
86
|
-
),
|
87
|
-
"average_time_per_interview": lambda: InterviewStatistic(
|
88
|
-
"average_time_per_interview",
|
89
|
-
value=elapsed_time / len(completed_tasks) if completed_tasks else "NA",
|
90
|
-
digits=1,
|
91
|
-
units="sec.",
|
92
|
-
),
|
93
|
-
"task_remaining": lambda: InterviewStatistic(
|
94
|
-
"task_remaining", value=len(interviews) - len(completed_tasks), units=""
|
95
|
-
),
|
96
|
-
"estimated_time_remaining": lambda: InterviewStatistic(
|
97
|
-
"estimated_time_remaining",
|
98
|
-
value=(
|
99
|
-
(len(interviews) - len(completed_tasks))
|
100
|
-
* (elapsed_time / len(completed_tasks))
|
101
|
-
if len(completed_tasks) > 0
|
102
|
-
else "NA"
|
103
|
-
),
|
104
|
-
digits=1,
|
105
|
-
units="sec.",
|
106
|
-
),
|
107
|
-
}
|
108
|
-
if stat_name not in stat_definitions:
|
109
|
-
raise ValueError(
|
110
|
-
f"Invalid stat_name: {stat_name}. The valid stat_names are: {list(stat_definitions.keys())}"
|
111
|
-
)
|
112
|
-
return stat_definitions[stat_name]()
|
113
|
-
|
114
|
-
@staticmethod
|
115
|
-
def _job_level_info(
|
116
|
-
completed_tasks: List[Type[asyncio.Task]],
|
117
|
-
elapsed_time: float,
|
118
|
-
interviews: List[Type["Interview"]],
|
119
|
-
) -> InterviewStatisticsCollection:
|
120
|
-
interview_statistics = InterviewStatisticsCollection()
|
121
|
-
|
122
|
-
default_statistics = [
|
123
|
-
"elapsed_time",
|
124
|
-
"total_interviews_requested",
|
125
|
-
"completed_interviews",
|
126
|
-
"percent_complete",
|
127
|
-
"average_time_per_interview",
|
128
|
-
"task_remaining",
|
129
|
-
"estimated_time_remaining",
|
130
|
-
]
|
131
|
-
for stat_name in default_statistics:
|
132
|
-
interview_statistics.add_stat(
|
133
|
-
JobsRunnerStatusMixin._compute_statistic(
|
134
|
-
stat_name, completed_tasks, elapsed_time, interviews
|
135
|
-
)
|
136
|
-
)
|
137
|
-
|
138
|
-
return interview_statistics
|
139
|
-
|
140
|
-
@staticmethod
|
141
|
-
def _get_model_queues_info(interviews):
|
142
|
-
models_to_tokens = defaultdict(InterviewTokenUsage)
|
143
|
-
model_to_status = defaultdict(InterviewStatusDictionary)
|
144
|
-
waiting_dict = defaultdict(int)
|
145
|
-
|
146
|
-
for interview in interviews:
|
147
|
-
models_to_tokens[interview.model] += interview.token_usage
|
148
|
-
model_to_status[interview.model] += interview.interview_status
|
149
|
-
waiting_dict[interview.model] += interview.interview_status.waiting
|
150
|
-
|
151
|
-
for model, num_waiting in waiting_dict.items():
|
152
|
-
yield JobsRunnerStatusMixin._get_model_info(
|
153
|
-
model, num_waiting, models_to_tokens
|
154
|
-
)
|
155
|
-
|
156
|
-
@staticmethod
|
157
|
-
def generate_status_summary(
|
158
|
-
completed_tasks: List[Type[asyncio.Task]],
|
159
|
-
elapsed_time: float,
|
160
|
-
interviews: List[Type["Interview"]],
|
161
|
-
include_model_queues=False,
|
162
|
-
) -> InterviewStatisticsCollection:
|
163
|
-
"""Generate a summary of the status of the job runner.
|
164
|
-
|
165
|
-
:param completed_tasks: list of completed tasks
|
166
|
-
:param elapsed_time: time elapsed since the start of the job
|
167
|
-
:param interviews: list of interviews to be conducted
|
168
|
-
|
169
|
-
>>> from edsl.jobs.interviews.Interview import Interview
|
170
|
-
>>> interviews = [Interview.example()]
|
171
|
-
>>> completed_tasks = []
|
172
|
-
>>> elapsed_time = 0
|
173
|
-
>>> JobsRunnerStatusMixin().generate_status_summary(completed_tasks, elapsed_time, interviews)
|
174
|
-
{'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'}
|
175
|
-
"""
|
176
|
-
|
177
|
-
interview_status_summary: InterviewStatisticsCollection = (
|
178
|
-
JobsRunnerStatusMixin._job_level_info(
|
179
|
-
completed_tasks=completed_tasks,
|
180
|
-
elapsed_time=elapsed_time,
|
181
|
-
interviews=interviews,
|
182
|
-
)
|
183
|
-
)
|
184
|
-
if include_model_queues:
|
185
|
-
interview_status_summary.model_queues = list(
|
186
|
-
JobsRunnerStatusMixin._get_model_queues_info(interviews)
|
187
|
-
)
|
188
|
-
else:
|
189
|
-
interview_status_summary.model_queues = None
|
190
|
-
|
191
|
-
return interview_status_summary
|
192
|
-
|
193
|
-
@staticmethod
|
194
|
-
def _get_model_info(
|
195
|
-
model: str,
|
196
|
-
num_waiting: int,
|
197
|
-
models_to_tokens: InterviewTokenUsageMapping,
|
198
|
-
) -> dict:
|
199
|
-
"""Get the status of a model.
|
200
|
-
|
201
|
-
:param model: the model name
|
202
|
-
:param num_waiting: the number of tasks waiting for capacity
|
203
|
-
:param models_to_tokens: a mapping of models to token usage
|
204
|
-
|
205
|
-
>>> from edsl.jobs.interviews.Interview import Interview
|
206
|
-
>>> interviews = [Interview.example()]
|
207
|
-
>>> models_to_tokens = defaultdict(InterviewTokenUsage)
|
208
|
-
>>> model = interviews[0].model
|
209
|
-
>>> num_waiting = 0
|
210
|
-
>>> JobsRunnerStatusMixin()._get_model_info(model, num_waiting, models_to_tokens)
|
211
|
-
ModelInfo(model_name='...', TPM_limit_k=..., RPM_limit_k=..., num_tasks_waiting=0, token_usage_info=[ModelTokenUsageStats(token_usage_type='new_token_usage', details=[{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], cost='$0.00000'), ModelTokenUsageStats(token_usage_type='cached_token_usage', details=[{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], cost='$0.00000')])
|
212
|
-
"""
|
213
|
-
|
214
|
-
## TODO: This should probably be a coop method
|
215
|
-
prices = get_token_pricing(model.model)
|
216
|
-
|
217
|
-
token_usage_info = []
|
218
|
-
for token_usage_type in ["new_token_usage", "cached_token_usage"]:
|
219
|
-
token_usage_info.append(
|
220
|
-
JobsRunnerStatusMixin._get_token_usage_info(
|
221
|
-
token_usage_type, models_to_tokens, model, prices
|
222
|
-
)
|
223
|
-
)
|
224
|
-
|
225
|
-
return ModelInfo(
|
226
|
-
**{
|
227
|
-
"model_name": model.model,
|
228
|
-
"TPM_limit_k": model.TPM / 1000,
|
229
|
-
"RPM_limit_k": model.RPM / 1000,
|
230
|
-
"num_tasks_waiting": num_waiting,
|
231
|
-
"token_usage_info": token_usage_info,
|
232
|
-
}
|
233
|
-
)
|
234
|
-
|
235
|
-
@staticmethod
|
236
|
-
def _get_token_usage_info(
|
237
|
-
token_usage_type: Literal["new_token_usage", "cached_token_usage"],
|
238
|
-
models_to_tokens: InterviewTokenUsageMapping,
|
239
|
-
model: str,
|
240
|
-
prices: "TokenPricing",
|
241
|
-
) -> ModelTokenUsageStats:
|
242
|
-
"""Get the token usage info for a model.
|
243
|
-
|
244
|
-
>>> from edsl.jobs.interviews.Interview import Interview
|
245
|
-
>>> interviews = [Interview.example()]
|
246
|
-
>>> models_to_tokens = defaultdict(InterviewTokenUsage)
|
247
|
-
>>> model = interviews[0].model
|
248
|
-
>>> prices = get_token_pricing(model.model)
|
249
|
-
>>> cache_status = "new_token_usage"
|
250
|
-
>>> JobsRunnerStatusMixin()._get_token_usage_info(cache_status, models_to_tokens, model, prices)
|
251
|
-
ModelTokenUsageStats(token_usage_type='new_token_usage', details=[{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], cost='$0.00000')
|
252
|
-
|
253
|
-
"""
|
254
|
-
all_token_usage: InterviewTokenUsage = models_to_tokens[model]
|
255
|
-
token_usage: TokenUsage = getattr(all_token_usage, token_usage_type)
|
256
|
-
|
257
|
-
details = [
|
258
|
-
{"type": token_type, "tokens": getattr(token_usage, token_type)}
|
259
|
-
for token_type in ["prompt_tokens", "completion_tokens"]
|
260
|
-
]
|
261
|
-
|
262
|
-
return ModelTokenUsageStats(
|
263
|
-
token_usage_type=token_usage_type,
|
264
|
-
details=details,
|
265
|
-
cost=f"${token_usage.cost(prices):.5f}",
|
266
|
-
)
|
267
|
-
|
268
|
-
@staticmethod
|
269
|
-
def _add_statistics_to_table(table, status_summary):
|
270
|
-
table.add_column("Statistic", style="dim", no_wrap=True, width=50)
|
271
|
-
table.add_column("Value", width=10)
|
272
|
-
|
273
|
-
for key, value in status_summary.items():
|
274
|
-
if key != "model_queues":
|
275
|
-
table.add_row(key, value)
|
276
|
-
|
277
|
-
@staticmethod
|
278
|
-
def display_status_table(status_summary: InterviewStatisticsCollection) -> "Table":
|
279
|
-
table = Table(
|
280
|
-
title="Job Status",
|
281
|
-
show_header=True,
|
282
|
-
header_style="bold magenta",
|
283
|
-
box=SIMPLE,
|
284
|
-
)
|
285
|
-
|
286
|
-
### Job-level statistics
|
287
|
-
JobsRunnerStatusMixin._add_statistics_to_table(table, status_summary)
|
288
|
-
|
289
|
-
## Model-level statistics
|
290
|
-
spacing = " "
|
291
|
-
|
292
|
-
if status_summary.model_queues is not None:
|
293
|
-
table.add_row(Text("Model Queues", style="bold red"), "")
|
294
|
-
for model_info in status_summary.model_queues:
|
295
|
-
model_name = model_info.model_name
|
296
|
-
tpm = f"TPM (k)={model_info.TPM_limit_k}"
|
297
|
-
rpm = f"RPM (k)= {model_info.RPM_limit_k}"
|
298
|
-
pretty_model_name = model_name + ";" + tpm + ";" + rpm
|
299
|
-
table.add_row(Text(pretty_model_name, style="blue"), "")
|
300
|
-
table.add_row(
|
301
|
-
"Number question tasks waiting for capacity",
|
302
|
-
str(model_info.num_tasks_waiting),
|
303
|
-
)
|
304
|
-
# Token usage and cost info
|
305
|
-
for token_usage_info in model_info.token_usage_info:
|
306
|
-
token_usage_type = token_usage_info.token_usage_type
|
307
|
-
table.add_row(
|
308
|
-
Text(
|
309
|
-
spacing + token_usage_type.replace("_", " "), style="bold"
|
310
|
-
),
|
311
|
-
"",
|
312
|
-
)
|
313
|
-
for detail in token_usage_info.details:
|
314
|
-
token_type = detail["type"]
|
315
|
-
tokens = detail["tokens"]
|
316
|
-
table.add_row(spacing + f"{token_type}", f"{tokens:,}")
|
317
|
-
# table.add_row(spacing + "cost", cache_info["cost"])
|
318
|
-
|
319
|
-
return table
|
320
|
-
|
321
|
-
def status_table(self, completed_tasks: List[asyncio.Task], elapsed_time: float):
|
322
|
-
summary_data = JobsRunnerStatusMixin.generate_status_summary(
|
323
|
-
completed_tasks=completed_tasks,
|
324
|
-
elapsed_time=elapsed_time,
|
325
|
-
interviews=self.total_interviews,
|
326
|
-
)
|
327
|
-
return self.display_status_table(summary_data)
|
328
|
-
|
329
|
-
|
330
|
-
if __name__ == "__main__":
|
331
|
-
import doctest
|
332
|
-
|
333
|
-
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
File without changes
|
File without changes
|