edsl 0.1.31.dev2__py3-none-any.whl → 0.1.31.dev4__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.
@@ -1,237 +0,0 @@
1
- import asyncio
2
- from enum import Enum
3
- from typing import Literal, List, Type, DefaultDict
4
- from collections import UserDict, defaultdict
5
-
6
- from edsl.jobs.interviews.InterviewStatusDictionary import InterviewStatusDictionary
7
- from edsl.jobs.tokens.InterviewTokenUsage import InterviewTokenUsage
8
-
9
- # from edsl.enums import pricing, TokenPricing
10
- from edsl.enums import get_token_pricing
11
- from edsl.jobs.tasks.task_status_enum import TaskStatus
12
-
13
- InterviewTokenUsageMapping = DefaultDict[str, InterviewTokenUsage]
14
-
15
- from edsl.jobs.interviews.InterviewStatistic import InterviewStatistic
16
- from edsl.jobs.interviews.InterviewStatisticsCollection import (
17
- InterviewStatisticsCollection,
18
- )
19
-
20
-
21
- class JobsRunnerStatusData:
22
- def status_dict(
23
- self, interviews: List[Type["Interview"]]
24
- ) -> List[Type[InterviewStatusDictionary]]:
25
- """
26
- >>> from edsl.jobs.interviews.Interview import Interview
27
- >>> interviews = [Interview.example()]
28
- >>> JobsRunnerStatusData().status_dict(interviews)
29
- [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})]
30
- """
31
- status = []
32
- for interview in interviews:
33
- status.append(interview.interview_status)
34
-
35
- return status
36
-
37
- def status_counts(self, interviews: List[Type["Interview"]]):
38
- """
39
- Takes a collection of interviews and returns a dictionary of the counts of each status.
40
-
41
- :param interviews: a collection of interviews.
42
-
43
- This creates a dictionary of the counts of each status in the collection of interviews.
44
-
45
- >>> from edsl.jobs.interviews.Interview import Interview
46
- >>> interviews = [Interview.example() for _ in range(100)]
47
- >>> jd = JobsRunnerStatusData()
48
- >>> jd.status_counts(interviews)
49
- dict_values([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})])
50
- >>> len(jd.status_counts(interviews))
51
- 1
52
- """
53
- model_to_status = defaultdict(InterviewStatusDictionary)
54
-
55
- for interview in interviews:
56
- model = interview.model # get the model for the interview
57
- model_to_status[
58
- model
59
- ] += (
60
- interview.interview_status
61
- ) # InterviewStatusDictionary objects can be added together
62
-
63
- return (
64
- model_to_status.values()
65
- ) # return the values of the dictionary, which is a list of dictionaries
66
-
67
- def generate_status_summary(
68
- self,
69
- completed_tasks: List[Type[asyncio.Task]],
70
- elapsed_time: float,
71
- interviews: List[Type["Interview"]],
72
- ) -> InterviewStatisticsCollection:
73
- """Generate a summary of the status of the job runner.
74
-
75
- :param completed_tasks: list of completed tasks
76
- :param elapsed_time: time elapsed since the start of the job
77
- :param interviews: list of interviews to be conducted
78
-
79
- >>> from edsl.jobs.interviews.Interview import Interview
80
- >>> interviews = [Interview.example()]
81
- >>> completed_tasks = []
82
- >>> elapsed_time = 0
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': '...', '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
- """
86
-
87
- models_to_tokens = defaultdict(InterviewTokenUsage)
88
- model_to_status = defaultdict(InterviewStatusDictionary)
89
-
90
- waiting_dict = defaultdict(int)
91
-
92
- interview_statistics = InterviewStatisticsCollection()
93
-
94
- for interview in interviews:
95
- model = interview.model
96
- models_to_tokens[model] += interview.token_usage
97
- model_to_status[model] += interview.interview_status
98
- waiting_dict[model] += interview.interview_status.waiting
99
-
100
- interview_statistics.add_stat(
101
- InterviewStatistic(
102
- "elapsed_time", value=elapsed_time, digits=1, units="sec."
103
- )
104
- )
105
- interview_statistics.add_stat(
106
- InterviewStatistic(
107
- "total_interviews_requested", value=len(interviews), units=""
108
- )
109
- )
110
- interview_statistics.add_stat(
111
- InterviewStatistic(
112
- "completed_interviews", value=len(completed_tasks), units=""
113
- )
114
- )
115
- interview_statistics.add_stat(
116
- InterviewStatistic(
117
- "percent_complete",
118
- value=(
119
- len(completed_tasks) / len(interviews) * 100
120
- if len(interviews) > 0
121
- else "NA"
122
- ),
123
- digits=0,
124
- units="%",
125
- )
126
- )
127
- interview_statistics.add_stat(
128
- InterviewStatistic(
129
- "average_time_per_interview",
130
- value=elapsed_time / len(completed_tasks) if completed_tasks else "NA",
131
- digits=1,
132
- units="sec.",
133
- )
134
- )
135
- interview_statistics.add_stat(
136
- InterviewStatistic(
137
- "task_remaining", value=len(interviews) - len(completed_tasks), units=""
138
- )
139
- )
140
- number_remaining = len(interviews) - len(completed_tasks)
141
- time_per_task = (
142
- elapsed_time / len(completed_tasks) if len(completed_tasks) > 0 else "NA"
143
- )
144
- estimated_time_remaining = (
145
- number_remaining * time_per_task if time_per_task != "NA" else "NA"
146
- )
147
-
148
- interview_statistics.add_stat(
149
- InterviewStatistic(
150
- "estimated_time_remaining",
151
- value=estimated_time_remaining,
152
- digits=1,
153
- units="sec.",
154
- )
155
- )
156
- model_queues_info = []
157
- for model, num_waiting in waiting_dict.items():
158
- model_info = self._get_model_info(model, num_waiting, models_to_tokens)
159
- model_queues_info.append(model_info)
160
-
161
- interview_statistics["model_queues"] = model_queues_info
162
-
163
- return interview_statistics
164
-
165
- def _get_model_info(
166
- self,
167
- model: str,
168
- num_waiting: int,
169
- models_to_tokens: InterviewTokenUsageMapping,
170
- ) -> dict:
171
- """Get the status of a model.
172
-
173
- >>> from edsl.jobs.interviews.Interview import Interview
174
- >>> interviews = [Interview.example()]
175
- >>> models_to_tokens = defaultdict(InterviewTokenUsage)
176
- >>> model = interviews[0].model
177
- >>> num_waiting = 0
178
- >>> JobsRunnerStatusData()._get_model_info(model, num_waiting, models_to_tokens)
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
- """
181
-
182
- prices = get_token_pricing(model.model)
183
-
184
- model_info = {
185
- "model_name": model.model,
186
- "TPM_limit_k": model.TPM / 1000,
187
- "RPM_limit_k": model.RPM / 1000,
188
- "num_tasks_waiting": num_waiting,
189
- "token_usage_info": [],
190
- }
191
-
192
- token_usage_types = ["new_token_usage", "cached_token_usage"]
193
- for token_usage_type in token_usage_types:
194
- cache_info = self._get_token_usage_info(
195
- token_usage_type, models_to_tokens, model, prices
196
- )
197
- model_info["token_usage_info"].append(cache_info)
198
-
199
- return model_info
200
-
201
- def _get_token_usage_info(
202
- self,
203
- cache_status: Literal["new_token_usage", "cached_token_usage"],
204
- models_to_tokens: InterviewTokenUsageMapping,
205
- model: str,
206
- prices: "TokenPricing",
207
- ) -> dict:
208
- """Get the token usage info for a model.
209
-
210
- >>> from edsl.jobs.interviews.Interview import Interview
211
- >>> interviews = [Interview.example()]
212
- >>> models_to_tokens = defaultdict(InterviewTokenUsage)
213
- >>> model = interviews[0].model
214
- >>> prices = get_token_pricing(model.model)
215
- >>> cache_status = "new_token_usage"
216
- >>> JobsRunnerStatusData()._get_token_usage_info(cache_status, models_to_tokens, model, prices)
217
- {'cache_status': 'new_token_usage', 'details': [{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], 'cost': '$0.00000'}
218
-
219
- """
220
- cache_info = {"cache_status": cache_status, "details": []}
221
- token_usage = getattr(models_to_tokens[model], cache_status)
222
- for token_type in ["prompt_tokens", "completion_tokens"]:
223
- tokens = getattr(token_usage, token_type)
224
- cache_info["details"].append(
225
- {
226
- "type": token_type,
227
- "tokens": tokens,
228
- }
229
- )
230
- cache_info["cost"] = f"${token_usage.cost(prices):.5f}"
231
- return cache_info
232
-
233
-
234
- if __name__ == "__main__":
235
- import doctest
236
-
237
- doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,27 +1,246 @@
1
1
  from __future__ import annotations
2
2
  from typing import List, DefaultDict
3
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
4
28
 
5
29
  from rich.text import Text
6
30
  from rich.box import SIMPLE
7
31
  from rich.table import Table
8
32
 
9
- from edsl.jobs.tokens.InterviewTokenUsage import InterviewTokenUsage
10
- from edsl.jobs.runners.JobsRunnerStatusData import JobsRunnerStatusData
33
+ @dataclass
34
+ class ModelInfo:
35
+ model_name: str
36
+ TPM_limit_k: float
37
+ RPM_limit_k: float
38
+ num_tasks_waiting: int
39
+ token_usage_info: dict
40
+
41
+
42
+ @dataclass
43
+ class ModelTokenUsageStats:
44
+ token_usage_type: str
45
+ details: List[dict]
46
+ cost: str
47
+
48
+ class Stats:
49
+
50
+ def elapsed_time(self):
51
+ InterviewStatistic(
52
+ "elapsed_time", value=elapsed_time, digits=1, units="sec."
53
+ )
54
+
55
+
11
56
 
12
- InterviewTokenUsageMapping = DefaultDict[str, InterviewTokenUsage]
57
+
58
+ class JobsRunnerStatusMixin:
59
+
60
+ # @staticmethod
61
+ # def status_dict(interviews: List[Type["Interview"]]) -> List[Type[InterviewStatusDictionary]]:
62
+ # """
63
+ # >>> from edsl.jobs.interviews.Interview import Interview
64
+ # >>> interviews = [Interview.example()]
65
+ # >>> JobsRunnerStatusMixin().status_dict(interviews)
66
+ # [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})]
67
+ # """
68
+ # return [interview.interview_status for interview in interviews]
69
+
70
+ def _compute_statistic(stat_name: str, completed_tasks, elapsed_time, interviews):
71
+
72
+ stat_definitions = {
73
+ "elapsed_time": lambda: InterviewStatistic(
74
+ "elapsed_time", value=elapsed_time, digits=1, units="sec."
75
+ ),
76
+ "total_interviews_requested": lambda: InterviewStatistic(
77
+ "total_interviews_requested", value=len(interviews), units=""
78
+ ),
79
+ "completed_interviews": lambda: InterviewStatistic(
80
+ "completed_interviews", value=len(completed_tasks), units=""
81
+ ),
82
+ "percent_complete": lambda: InterviewStatistic(
83
+ "percent_complete",
84
+ value=(
85
+ len(completed_tasks) / len(interviews) * 100
86
+ if len(interviews) > 0
87
+ else "NA"
88
+ ),
89
+ digits=0,
90
+ units="%",
91
+ ),
92
+ "average_time_per_interview": lambda: InterviewStatistic(
93
+ "average_time_per_interview",
94
+ value=elapsed_time / len(completed_tasks) if completed_tasks else "NA",
95
+ digits=1,
96
+ units="sec.",
97
+ ),
98
+ "task_remaining": lambda: InterviewStatistic(
99
+ "task_remaining", value=len(interviews) - len(completed_tasks), units=""
100
+ ),
101
+ "estimated_time_remaining": lambda: InterviewStatistic(
102
+ "estimated_time_remaining",
103
+ value=(
104
+ (len(interviews) - len(completed_tasks)) * (elapsed_time / len(completed_tasks))
105
+ if len(completed_tasks) > 0
106
+ else "NA"
107
+ ),
108
+ digits=1,
109
+ units="sec.",
110
+ )
111
+ }
112
+ if stat_name not in stat_definitions:
113
+ raise ValueError(f"Invalid stat_name: {stat_name}. The valid stat_names are: {list(stat_definitions.keys())}")
114
+ return stat_definitions[stat_name]()
13
115
 
14
116
 
15
- class JobsRunnerStatusPresentation:
16
117
  @staticmethod
17
- def display_status_table(status_summary):
18
- table = Table(
19
- title="Job Status",
20
- show_header=True,
21
- header_style="bold magenta",
22
- box=SIMPLE,
118
+ def _job_level_info(completed_tasks: List[Type[asyncio.Task]],
119
+ elapsed_time: float,
120
+ interviews: List[Type["Interview"]]
121
+ ) -> InterviewStatisticsCollection:
122
+
123
+ interview_statistics = InterviewStatisticsCollection()
124
+
125
+ default_statistics = ["elapsed_time", "total_interviews_requested", "completed_interviews", "percent_complete", "average_time_per_interview", "task_remaining", "estimated_time_remaining"]
126
+ for stat_name in default_statistics:
127
+ interview_statistics.add_stat(JobsRunnerStatusMixin._compute_statistic(stat_name, completed_tasks, elapsed_time, interviews))
128
+
129
+ return interview_statistics
130
+
131
+ @staticmethod
132
+ def _get_model_queues_info(interviews):
133
+
134
+ models_to_tokens = defaultdict(InterviewTokenUsage)
135
+ model_to_status = defaultdict(InterviewStatusDictionary)
136
+ waiting_dict = defaultdict(int)
137
+
138
+ for interview in interviews:
139
+ models_to_tokens[interview.model] += interview.token_usage
140
+ model_to_status[interview.model] += interview.interview_status
141
+ waiting_dict[interview.model] += interview.interview_status.waiting
142
+
143
+ for model, num_waiting in waiting_dict.items():
144
+ yield JobsRunnerStatusMixin._get_model_info(model, num_waiting, models_to_tokens)
145
+
146
+ @staticmethod
147
+ def generate_status_summary(
148
+ completed_tasks: List[Type[asyncio.Task]],
149
+ elapsed_time: float,
150
+ interviews: List[Type["Interview"]],
151
+ include_model_queues = False
152
+ ) -> InterviewStatisticsCollection:
153
+ """Generate a summary of the status of the job runner.
154
+
155
+ :param completed_tasks: list of completed tasks
156
+ :param elapsed_time: time elapsed since the start of the job
157
+ :param interviews: list of interviews to be conducted
158
+
159
+ >>> from edsl.jobs.interviews.Interview import Interview
160
+ >>> interviews = [Interview.example()]
161
+ >>> completed_tasks = []
162
+ >>> elapsed_time = 0
163
+ >>> JobsRunnerStatusMixin().generate_status_summary(completed_tasks, elapsed_time, interviews)
164
+ {'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'}
165
+ """
166
+
167
+ interview_status_summary: InterviewStatisticsCollection = JobsRunnerStatusMixin._job_level_info(
168
+ completed_tasks=completed_tasks,
169
+ elapsed_time=elapsed_time,
170
+ interviews=interviews
23
171
  )
24
- table.max_width = 100
172
+ if include_model_queues:
173
+ interview_status_summary.model_queues = list(JobsRunnerStatusMixin._get_model_queues_info(interviews))
174
+ else:
175
+ interview_status_summary.model_queues = None
176
+
177
+ return interview_status_summary
178
+
179
+ @staticmethod
180
+ def _get_model_info(
181
+ model: str,
182
+ num_waiting: int,
183
+ models_to_tokens: InterviewTokenUsageMapping,
184
+ ) -> dict:
185
+ """Get the status of a model.
186
+
187
+ :param model: the model name
188
+ :param num_waiting: the number of tasks waiting for capacity
189
+ :param models_to_tokens: a mapping of models to token usage
190
+
191
+ >>> from edsl.jobs.interviews.Interview import Interview
192
+ >>> interviews = [Interview.example()]
193
+ >>> models_to_tokens = defaultdict(InterviewTokenUsage)
194
+ >>> model = interviews[0].model
195
+ >>> num_waiting = 0
196
+ >>> JobsRunnerStatusMixin()._get_model_info(model, num_waiting, models_to_tokens)
197
+ ModelInfo(model_name='gpt-4-1106-preview', TPM_limit_k=480.0, RPM_limit_k=4.0, 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')])
198
+ """
199
+
200
+ ## TODO: This should probably be a coop method
201
+ prices = get_token_pricing(model.model)
202
+
203
+ token_usage_info = []
204
+ for token_usage_type in ["new_token_usage", "cached_token_usage"]:
205
+ token_usage_info.append(JobsRunnerStatusMixin._get_token_usage_info(token_usage_type, models_to_tokens, model, prices))
206
+
207
+ return ModelInfo(**{
208
+ "model_name": model.model,
209
+ "TPM_limit_k": model.TPM / 1000,
210
+ "RPM_limit_k": model.RPM / 1000,
211
+ "num_tasks_waiting": num_waiting,
212
+ "token_usage_info": token_usage_info,
213
+ })
214
+
215
+ @staticmethod
216
+ def _get_token_usage_info(
217
+ token_usage_type: Literal["new_token_usage", "cached_token_usage"],
218
+ models_to_tokens: InterviewTokenUsageMapping,
219
+ model: str,
220
+ prices: "TokenPricing",
221
+ ) -> ModelTokenUsageStats:
222
+ """Get the token usage info for a model.
223
+
224
+ >>> from edsl.jobs.interviews.Interview import Interview
225
+ >>> interviews = [Interview.example()]
226
+ >>> models_to_tokens = defaultdict(InterviewTokenUsage)
227
+ >>> model = interviews[0].model
228
+ >>> prices = get_token_pricing(model.model)
229
+ >>> cache_status = "new_token_usage"
230
+ >>> JobsRunnerStatusMixin()._get_token_usage_info(cache_status, models_to_tokens, model, prices)
231
+ ModelTokenUsageStats(token_usage_type='new_token_usage', details=[{'type': 'prompt_tokens', 'tokens': 0}, {'type': 'completion_tokens', 'tokens': 0}], cost='$0.00000')
232
+
233
+ """
234
+ all_token_usage: InterviewTokenUsage = models_to_tokens[model]
235
+ token_usage: TokenUsage = getattr(all_token_usage, token_usage_type)
236
+
237
+ details = [{"type": token_type, "tokens": getattr(token_usage, token_type)}
238
+ for token_type in ["prompt_tokens", "completion_tokens"]]
239
+
240
+ return ModelTokenUsageStats(token_usage_type = token_usage_type, details = details, cost = f"${token_usage.cost(prices):.5f}")
241
+
242
+ @staticmethod
243
+ def _add_statistics_to_table(table, status_summary):
25
244
  table.add_column("Statistic", style="dim", no_wrap=True, width=50)
26
245
  table.add_column("Value", width=10)
27
246
 
@@ -29,49 +248,56 @@ class JobsRunnerStatusPresentation:
29
248
  if key != "model_queues":
30
249
  table.add_row(key, value)
31
250
 
251
+ @staticmethod
252
+ def display_status_table(status_summary: InterviewStatisticsCollection) -> 'Table':
253
+
254
+
255
+ table = Table(
256
+ title="Job Status",
257
+ show_header=True,
258
+ header_style="bold magenta",
259
+ box=SIMPLE,
260
+ )
261
+
262
+ ### Job-level statistics
263
+ JobsRunnerStatusMixin._add_statistics_to_table(table, status_summary)
264
+
265
+ ## Model-level statistics
32
266
  spacing = " "
33
- if "model_queues" in status_summary:
267
+
268
+ if status_summary.model_queues is not None:
34
269
  table.add_row(Text("Model Queues", style="bold red"), "")
35
- for model_info in status_summary["model_queues"]:
36
- model_name = model_info["model_name"]
37
- tpm = "TPM (k)=" + str(model_info["TPM_limit_k"])
38
- rpm = "RPM (k)=" + str(model_info["RPM_limit_k"])
270
+ for model_info in status_summary.model_queues:
271
+
272
+ model_name = model_info.model_name
273
+ tpm = f"TPM (k)={model_info.TPM_limit_k}"
274
+ rpm = f"RPM (k)= {model_info.RPM_limit_k}"
39
275
  pretty_model_name = model_name + ";" + tpm + ";" + rpm
40
276
  table.add_row(Text(pretty_model_name, style="blue"), "")
41
- table.add_row(
42
- "Number question tasks waiting for capacity",
43
- str(model_info["num_tasks_waiting"]),
44
- )
277
+ table.add_row("Number question tasks waiting for capacity", str(model_info.num_tasks_waiting))
45
278
  # Token usage and cost info
46
- for cache_info in model_info["token_usage_info"]:
47
- cache_status = cache_info["cache_status"]
279
+ for token_usage_info in model_info.token_usage_info:
280
+ token_usage_type = token_usage_info.token_usage_type
48
281
  table.add_row(
49
- Text(spacing + cache_status.replace("_", " "), style="bold"), ""
282
+ Text(spacing + token_usage_type.replace("_", " "), style="bold"), ""
50
283
  )
51
- for detail in cache_info["details"]:
284
+ for detail in token_usage_info.details:
52
285
  token_type = detail["type"]
53
286
  tokens = detail["tokens"]
54
- # cost = detail["cost"]
55
287
  table.add_row(spacing + f"{token_type}", f"{tokens:,}")
56
- table.add_row(spacing + "cost", cache_info["cost"])
288
+ #table.add_row(spacing + "cost", cache_info["cost"])
57
289
 
58
290
  return table
59
291
 
60
-
61
- class JobsRunnerStatusMixin(JobsRunnerStatusData, JobsRunnerStatusPresentation):
62
- def status_data(self, completed_tasks: List[asyncio.Task], elapsed_time: float):
63
- # return self.generate_status_summary(
64
- # completed_tasks=completed_tasks,
65
- # elapsed_time=elapsed_time,
66
- # interviews=self.total_interviews).rawplt.figure(figsize=(10, 6))
67
-
68
- # return self.full_status(self.total_interviews)
69
- return None
70
-
71
292
  def status_table(self, completed_tasks: List[asyncio.Task], elapsed_time: float):
72
- summary_data = self.generate_status_summary(
293
+ summary_data = JobsRunnerStatusMixin.generate_status_summary(
73
294
  completed_tasks=completed_tasks,
74
295
  elapsed_time=elapsed_time,
75
296
  interviews=self.total_interviews,
76
297
  )
77
298
  return self.display_status_table(summary_data)
299
+
300
+ if __name__ == "__main__":
301
+ import doctest
302
+
303
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -17,7 +17,13 @@ class TaskCreators(UserDict):
17
17
 
18
18
  @property
19
19
  def token_usage(self) -> InterviewTokenUsage:
20
- """Determines how many tokens were used for the interview."""
20
+ """Determines how many tokens were used for the interview.
21
+
22
+ This is iterates through all tasks that make up an interview.
23
+ For each task, it determines how many tokens were used and whether they were cached or new.
24
+ It then sums the total number of cached and new tokens used for the interview.
25
+
26
+ """
21
27
  cached_tokens = TokenUsage(from_cache=True)
22
28
  new_tokens = TokenUsage(from_cache=False)
23
29
  for task_creator in self.values():
@@ -28,7 +34,7 @@ class TaskCreators(UserDict):
28
34
  new_token_usage=new_tokens, cached_token_usage=cached_tokens
29
35
  )
30
36
 
31
- def print(self):
37
+ def print(self) -> None:
32
38
  from rich import print
33
39
 
34
40
  print({task.get_name(): task.task_status for task in self.values()})
@@ -122,6 +122,11 @@ class LanguageModel(
122
122
  # Skip the API key check. Sometimes this is useful for testing.
123
123
  self._api_token = None
124
124
 
125
+ def ask_question(self, question):
126
+ user_prompt = question.get_instructions().render(question.data).text
127
+ system_prompt = "You are a helpful agent pretending to be a human."
128
+ return self.execute_model_call(user_prompt, system_prompt)
129
+
125
130
  @property
126
131
  def api_token(self) -> str:
127
132
  if not hasattr(self, "_api_token"):
@@ -154,7 +159,8 @@ class LanguageModel(
154
159
  if verbose:
155
160
  print(f"Current key is {masked}")
156
161
  return self.execute_model_call(
157
- user_prompt="Hello, model!", system_prompt="You are a helpful agent."
162
+ user_prompt="Hello, model!",
163
+ system_prompt="You are a helpful agent."
158
164
  )
159
165
 
160
166
  def has_valid_api_key(self) -> bool:
@@ -36,6 +36,10 @@ class Model(metaclass=Meta):
36
36
  from edsl.inference_services.registry import default
37
37
 
38
38
  registry = registry or default
39
+
40
+ if isinstance(model_name, int):
41
+ model_name = cls.available(name_only=True)[model_name]
42
+
39
43
  factory = registry.create_model_factory(model_name)
40
44
  return factory(*args, **kwargs)
41
45
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edsl
3
- Version: 0.1.31.dev2
3
+ Version: 0.1.31.dev4
4
4
  Summary: Create and analyze LLM-based surveys
5
5
  Home-page: https://www.expectedparrot.com/
6
6
  License: MIT
@@ -19,6 +19,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Requires-Dist: aiohttp (>=3.9.1,<4.0.0)
20
20
  Requires-Dist: anthropic (>=0.23.1,<0.24.0)
21
21
  Requires-Dist: black[jupyter] (>=24.4.2,<25.0.0)
22
+ Requires-Dist: groq (>=0.9.0,<0.10.0)
22
23
  Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
23
24
  Requires-Dist: jupyter (>=1.0.0,<2.0.0)
24
25
  Requires-Dist: markdown2 (>=2.4.11,<3.0.0)