edsl 0.1.38__py3-none-any.whl → 0.1.38.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 +31 -60
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +9 -18
- edsl/agents/AgentList.py +8 -59
- edsl/agents/Invigilator.py +7 -18
- edsl/agents/InvigilatorBase.py +19 -0
- edsl/agents/PromptConstructor.py +4 -5
- edsl/config.py +0 -8
- edsl/coop/coop.py +7 -74
- edsl/data/Cache.py +2 -27
- edsl/data/CacheEntry.py +3 -8
- edsl/data/RemoteCacheSync.py +19 -0
- edsl/enums.py +0 -2
- edsl/inference_services/GoogleService.py +15 -7
- edsl/inference_services/registry.py +0 -2
- edsl/jobs/Jobs.py +548 -88
- edsl/jobs/interviews/Interview.py +11 -11
- edsl/jobs/runners/JobsRunnerAsyncio.py +35 -140
- edsl/jobs/runners/JobsRunnerStatus.py +2 -0
- edsl/jobs/tasks/TaskHistory.py +16 -15
- edsl/language_models/LanguageModel.py +84 -44
- edsl/language_models/ModelList.py +1 -47
- edsl/language_models/registry.py +4 -57
- edsl/prompts/Prompt.py +3 -8
- edsl/questions/QuestionBase.py +16 -20
- edsl/questions/QuestionExtract.py +4 -3
- edsl/questions/question_registry.py +6 -36
- edsl/results/Dataset.py +15 -146
- edsl/results/DatasetExportMixin.py +217 -231
- edsl/results/DatasetTree.py +4 -134
- edsl/results/Result.py +9 -18
- edsl/results/Results.py +51 -145
- edsl/scenarios/FileStore.py +13 -187
- edsl/scenarios/Scenario.py +4 -61
- edsl/scenarios/ScenarioList.py +62 -237
- edsl/surveys/Survey.py +2 -16
- edsl/surveys/SurveyFlowVisualizationMixin.py +9 -67
- edsl/surveys/instructions/Instruction.py +0 -12
- edsl/templates/error_reporting/interview_details.html +3 -3
- edsl/templates/error_reporting/interviews.html +9 -18
- edsl/utilities/utilities.py +0 -15
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/METADATA +1 -2
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/RECORD +45 -53
- edsl/inference_services/PerplexityService.py +0 -163
- edsl/jobs/JobsChecks.py +0 -147
- edsl/jobs/JobsPrompts.py +0 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +0 -239
- edsl/results/CSSParameterizer.py +0 -108
- edsl/results/TableDisplay.py +0 -198
- edsl/results/table_display.css +0 -78
- edsl/scenarios/ScenarioJoin.py +0 -127
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/WHEEL +0 -0
@@ -110,9 +110,9 @@ class Interview:
|
|
110
110
|
self.debug = debug
|
111
111
|
self.iteration = iteration
|
112
112
|
self.cache = cache
|
113
|
-
self.answers: dict[
|
114
|
-
|
115
|
-
|
113
|
+
self.answers: dict[str, str] = (
|
114
|
+
Answers()
|
115
|
+
) # will get filled in as interview progresses
|
116
116
|
self.sidecar_model = sidecar_model
|
117
117
|
|
118
118
|
# Trackers
|
@@ -143,9 +143,9 @@ class Interview:
|
|
143
143
|
The keys are the question names; the values are the lists of status log changes for each task.
|
144
144
|
"""
|
145
145
|
for task_creator in self.task_creators.values():
|
146
|
-
self._task_status_log_dict[
|
147
|
-
task_creator.
|
148
|
-
|
146
|
+
self._task_status_log_dict[task_creator.question.question_name] = (
|
147
|
+
task_creator.status_log
|
148
|
+
)
|
149
149
|
return self._task_status_log_dict
|
150
150
|
|
151
151
|
@property
|
@@ -486,11 +486,11 @@ class Interview:
|
|
486
486
|
"""
|
487
487
|
current_question_index: int = self.to_index[current_question.question_name]
|
488
488
|
|
489
|
-
next_question: Union[
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
489
|
+
next_question: Union[int, EndOfSurvey] = (
|
490
|
+
self.survey.rule_collection.next_question(
|
491
|
+
q_now=current_question_index,
|
492
|
+
answers=self.answers | self.scenario | self.agent["traits"],
|
493
|
+
)
|
494
494
|
)
|
495
495
|
|
496
496
|
next_question_index = next_question.next_q
|
@@ -4,7 +4,6 @@ import asyncio
|
|
4
4
|
import threading
|
5
5
|
import warnings
|
6
6
|
from typing import Coroutine, List, AsyncGenerator, Optional, Union, Generator, Type
|
7
|
-
from uuid import UUID
|
8
7
|
from collections import UserList
|
9
8
|
|
10
9
|
from edsl.results.Results import Results
|
@@ -37,8 +36,6 @@ class JobsRunnerAsyncio:
|
|
37
36
|
The Jobs object is a collection of interviews that are to be run.
|
38
37
|
"""
|
39
38
|
|
40
|
-
MAX_CONCURRENT_DEFAULT = 500
|
41
|
-
|
42
39
|
def __init__(self, jobs: "Jobs"):
|
43
40
|
self.jobs = jobs
|
44
41
|
self.interviews: List["Interview"] = jobs.interviews()
|
@@ -46,53 +43,6 @@ class JobsRunnerAsyncio:
|
|
46
43
|
self.total_interviews: List["Interview"] = []
|
47
44
|
self._initialized = threading.Event()
|
48
45
|
|
49
|
-
from edsl.config import CONFIG
|
50
|
-
|
51
|
-
self.MAX_CONCURRENT = int(CONFIG.get("EDSL_MAX_CONCURRENT_TASKS"))
|
52
|
-
# print(f"MAX_CONCURRENT: {self.MAX_CONCURRENT}")
|
53
|
-
|
54
|
-
# async def run_async_generator(
|
55
|
-
# self,
|
56
|
-
# cache: Cache,
|
57
|
-
# n: int = 1,
|
58
|
-
# stop_on_exception: bool = False,
|
59
|
-
# sidecar_model: Optional[LanguageModel] = None,
|
60
|
-
# total_interviews: Optional[List["Interview"]] = None,
|
61
|
-
# raise_validation_errors: bool = False,
|
62
|
-
# ) -> AsyncGenerator["Result", None]:
|
63
|
-
# """Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
64
|
-
|
65
|
-
# Completed tasks are yielded as they are completed.
|
66
|
-
|
67
|
-
# :param n: how many times to run each interview
|
68
|
-
# :param stop_on_exception: Whether to stop the interview if an exception is raised
|
69
|
-
# :param sidecar_model: a language model to use in addition to the interview's model
|
70
|
-
# :param total_interviews: A list of interviews to run can be provided instead.
|
71
|
-
# :param raise_validation_errors: Whether to raise validation errors
|
72
|
-
# """
|
73
|
-
# tasks = []
|
74
|
-
# if total_interviews: # was already passed in total interviews
|
75
|
-
# self.total_interviews = total_interviews
|
76
|
-
# else:
|
77
|
-
# self.total_interviews = list(
|
78
|
-
# self._populate_total_interviews(n=n)
|
79
|
-
# ) # Populate self.total_interviews before creating tasks
|
80
|
-
# self._initialized.set() # Signal that we're ready
|
81
|
-
|
82
|
-
# for interview in self.total_interviews:
|
83
|
-
# interviewing_task = self._build_interview_task(
|
84
|
-
# interview=interview,
|
85
|
-
# stop_on_exception=stop_on_exception,
|
86
|
-
# sidecar_model=sidecar_model,
|
87
|
-
# raise_validation_errors=raise_validation_errors,
|
88
|
-
# )
|
89
|
-
# tasks.append(asyncio.create_task(interviewing_task))
|
90
|
-
|
91
|
-
# for task in asyncio.as_completed(tasks):
|
92
|
-
# result = await task
|
93
|
-
# self.jobs_runner_status.add_completed_interview(result)
|
94
|
-
# yield result
|
95
|
-
|
96
46
|
async def run_async_generator(
|
97
47
|
self,
|
98
48
|
cache: Cache,
|
@@ -102,10 +52,9 @@ class JobsRunnerAsyncio:
|
|
102
52
|
total_interviews: Optional[List["Interview"]] = None,
|
103
53
|
raise_validation_errors: bool = False,
|
104
54
|
) -> AsyncGenerator["Result", None]:
|
105
|
-
"""Creates
|
55
|
+
"""Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
106
56
|
|
107
|
-
|
108
|
-
Results are yielded as soon as they are available.
|
57
|
+
Completed tasks are yielded as they are completed.
|
109
58
|
|
110
59
|
:param n: how many times to run each interview
|
111
60
|
:param stop_on_exception: Whether to stop the interview if an exception is raised
|
@@ -113,70 +62,29 @@ class JobsRunnerAsyncio:
|
|
113
62
|
:param total_interviews: A list of interviews to run can be provided instead.
|
114
63
|
:param raise_validation_errors: Whether to raise validation errors
|
115
64
|
"""
|
116
|
-
|
117
|
-
if total_interviews:
|
118
|
-
interviews_iter = iter(total_interviews)
|
65
|
+
tasks = []
|
66
|
+
if total_interviews: # was already passed in total interviews
|
119
67
|
self.total_interviews = total_interviews
|
120
68
|
else:
|
121
|
-
|
122
|
-
|
123
|
-
|
69
|
+
self.total_interviews = list(
|
70
|
+
self._populate_total_interviews(n=n)
|
71
|
+
) # Populate self.total_interviews before creating tasks
|
124
72
|
|
125
73
|
self._initialized.set() # Signal that we're ready
|
126
74
|
|
127
|
-
|
128
|
-
|
75
|
+
for interview in self.total_interviews:
|
76
|
+
interviewing_task = self._build_interview_task(
|
77
|
+
interview=interview,
|
78
|
+
stop_on_exception=stop_on_exception,
|
79
|
+
sidecar_model=sidecar_model,
|
80
|
+
raise_validation_errors=raise_validation_errors,
|
81
|
+
)
|
82
|
+
tasks.append(asyncio.create_task(interviewing_task))
|
129
83
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
try:
|
135
|
-
interview = next(interviews_iter)
|
136
|
-
task = asyncio.create_task(
|
137
|
-
self._build_interview_task(
|
138
|
-
interview=interview,
|
139
|
-
stop_on_exception=stop_on_exception,
|
140
|
-
sidecar_model=sidecar_model,
|
141
|
-
raise_validation_errors=raise_validation_errors,
|
142
|
-
)
|
143
|
-
)
|
144
|
-
active_tasks.add(task)
|
145
|
-
# Add callback to remove task from set when done
|
146
|
-
task.add_done_callback(active_tasks.discard)
|
147
|
-
except StopIteration:
|
148
|
-
break
|
149
|
-
|
150
|
-
if not active_tasks:
|
151
|
-
break
|
152
|
-
|
153
|
-
# Wait for next completed task
|
154
|
-
done, _ = await asyncio.wait(
|
155
|
-
active_tasks, return_when=asyncio.FIRST_COMPLETED
|
156
|
-
)
|
157
|
-
|
158
|
-
# Process completed tasks
|
159
|
-
for task in done:
|
160
|
-
try:
|
161
|
-
result = await task
|
162
|
-
self.jobs_runner_status.add_completed_interview(result)
|
163
|
-
yield result
|
164
|
-
except Exception as e:
|
165
|
-
if stop_on_exception:
|
166
|
-
# Cancel remaining tasks
|
167
|
-
for t in active_tasks:
|
168
|
-
if not t.done():
|
169
|
-
t.cancel()
|
170
|
-
raise
|
171
|
-
else:
|
172
|
-
# Log error and continue
|
173
|
-
# logger.error(f"Task failed with error: {e}")
|
174
|
-
continue
|
175
|
-
finally:
|
176
|
-
# Ensure we cancel any remaining tasks if we exit early
|
177
|
-
for task in active_tasks:
|
178
|
-
if not task.done():
|
179
|
-
task.cancel()
|
84
|
+
for task in asyncio.as_completed(tasks):
|
85
|
+
result = await task
|
86
|
+
self.jobs_runner_status.add_completed_interview(result)
|
87
|
+
yield result
|
180
88
|
|
181
89
|
def _populate_total_interviews(
|
182
90
|
self, n: int = 1
|
@@ -260,20 +168,20 @@ class JobsRunnerAsyncio:
|
|
260
168
|
|
261
169
|
prompt_dictionary = {}
|
262
170
|
for answer_key_name in answer_key_names:
|
263
|
-
prompt_dictionary[
|
264
|
-
answer_key_name
|
265
|
-
|
266
|
-
prompt_dictionary[
|
267
|
-
answer_key_name
|
268
|
-
|
171
|
+
prompt_dictionary[answer_key_name + "_user_prompt"] = (
|
172
|
+
question_name_to_prompts[answer_key_name]["user_prompt"]
|
173
|
+
)
|
174
|
+
prompt_dictionary[answer_key_name + "_system_prompt"] = (
|
175
|
+
question_name_to_prompts[answer_key_name]["system_prompt"]
|
176
|
+
)
|
269
177
|
|
270
178
|
raw_model_results_dictionary = {}
|
271
179
|
cache_used_dictionary = {}
|
272
180
|
for result in valid_results:
|
273
181
|
question_name = result.question_name
|
274
|
-
raw_model_results_dictionary[
|
275
|
-
|
276
|
-
|
182
|
+
raw_model_results_dictionary[question_name + "_raw_model_response"] = (
|
183
|
+
result.raw_model_response
|
184
|
+
)
|
277
185
|
raw_model_results_dictionary[question_name + "_cost"] = result.cost
|
278
186
|
one_use_buys = (
|
279
187
|
"NA"
|
@@ -337,25 +245,11 @@ class JobsRunnerAsyncio:
|
|
337
245
|
if len(results.task_history.indices) > 5:
|
338
246
|
msg += f"Exceptions were raised in the following interviews: {results.task_history.indices}.\n"
|
339
247
|
|
340
|
-
|
341
|
-
|
342
|
-
print(msg, file=sys.stderr)
|
343
|
-
from edsl.config import CONFIG
|
344
|
-
|
345
|
-
if CONFIG.get("EDSL_OPEN_EXCEPTION_REPORT_URL") == "True":
|
346
|
-
open_in_browser = True
|
347
|
-
elif CONFIG.get("EDSL_OPEN_EXCEPTION_REPORT_URL") == "False":
|
348
|
-
open_in_browser = False
|
349
|
-
else:
|
350
|
-
raise Exception(
|
351
|
-
"EDSL_OPEN_EXCEPTION_REPORT_URL", "must be either True or False"
|
352
|
-
)
|
353
|
-
|
354
|
-
# print("open_in_browser", open_in_browser)
|
355
|
-
|
248
|
+
print(msg)
|
249
|
+
# this is where exceptions are opening up
|
356
250
|
filepath = results.task_history.html(
|
357
251
|
cta="Open report to see details.",
|
358
|
-
open_in_browser=
|
252
|
+
open_in_browser=True,
|
359
253
|
return_link=True,
|
360
254
|
)
|
361
255
|
|
@@ -385,7 +279,6 @@ class JobsRunnerAsyncio:
|
|
385
279
|
progress_bar: bool = False,
|
386
280
|
sidecar_model: Optional[LanguageModel] = None,
|
387
281
|
jobs_runner_status: Optional[Type[JobsRunnerStatusBase]] = None,
|
388
|
-
job_uuid: Optional[UUID] = None,
|
389
282
|
print_exceptions: bool = True,
|
390
283
|
raise_validation_errors: bool = False,
|
391
284
|
) -> "Coroutine":
|
@@ -404,11 +297,13 @@ class JobsRunnerAsyncio:
|
|
404
297
|
|
405
298
|
if jobs_runner_status is not None:
|
406
299
|
self.jobs_runner_status = jobs_runner_status(
|
407
|
-
self, n=n, endpoint_url=endpoint_url
|
300
|
+
self, n=n, endpoint_url=endpoint_url
|
408
301
|
)
|
409
302
|
else:
|
410
303
|
self.jobs_runner_status = JobsRunnerStatus(
|
411
|
-
self,
|
304
|
+
self,
|
305
|
+
n=n,
|
306
|
+
endpoint_url=endpoint_url,
|
412
307
|
)
|
413
308
|
|
414
309
|
stop_event = threading.Event()
|
@@ -239,6 +239,7 @@ class JobsRunnerStatusBase(ABC):
|
|
239
239
|
return stat_definitions[stat_name]()
|
240
240
|
|
241
241
|
def update_progress(self, stop_event):
|
242
|
+
|
242
243
|
while not stop_event.is_set():
|
243
244
|
self.send_status_update()
|
244
245
|
time.sleep(self.refresh_rate)
|
@@ -247,6 +248,7 @@ class JobsRunnerStatusBase(ABC):
|
|
247
248
|
|
248
249
|
|
249
250
|
class JobsRunnerStatus(JobsRunnerStatusBase):
|
251
|
+
|
250
252
|
@property
|
251
253
|
def create_url(self) -> str:
|
252
254
|
return f"{self.base_url}/api/v0/local-job"
|
edsl/jobs/tasks/TaskHistory.py
CHANGED
@@ -8,12 +8,7 @@ from edsl.jobs.tasks.task_status_enum import TaskStatus
|
|
8
8
|
|
9
9
|
|
10
10
|
class TaskHistory:
|
11
|
-
def __init__(
|
12
|
-
self,
|
13
|
-
interviews: List["Interview"],
|
14
|
-
include_traceback: bool = False,
|
15
|
-
max_interviews: int = 10,
|
16
|
-
):
|
11
|
+
def __init__(self, interviews: List["Interview"], include_traceback: bool = False):
|
17
12
|
"""
|
18
13
|
The structure of a TaskHistory exception
|
19
14
|
|
@@ -27,7 +22,6 @@ class TaskHistory:
|
|
27
22
|
self.include_traceback = include_traceback
|
28
23
|
|
29
24
|
self._interviews = {index: i for index, i in enumerate(self.total_interviews)}
|
30
|
-
self.max_interviews = max_interviews
|
31
25
|
|
32
26
|
@classmethod
|
33
27
|
def example(cls):
|
@@ -81,6 +75,13 @@ class TaskHistory:
|
|
81
75
|
|
82
76
|
def to_dict(self, add_edsl_version=True):
|
83
77
|
"""Return the TaskHistory as a dictionary."""
|
78
|
+
# return {
|
79
|
+
# "exceptions": [
|
80
|
+
# e.to_dict(include_traceback=self.include_traceback)
|
81
|
+
# for e in self.exceptions
|
82
|
+
# ],
|
83
|
+
# "indices": self.indices,
|
84
|
+
# }
|
84
85
|
d = {
|
85
86
|
"interviews": [
|
86
87
|
i.to_dict(add_edsl_version=add_edsl_version)
|
@@ -123,11 +124,10 @@ class TaskHistory:
|
|
123
124
|
|
124
125
|
def _repr_html_(self):
|
125
126
|
"""Return an HTML representation of the TaskHistory."""
|
126
|
-
|
127
|
-
data = [[k, v] for k, v in d.items()]
|
128
|
-
from tabulate import tabulate
|
127
|
+
from edsl.utilities.utilities import data_to_html
|
129
128
|
|
130
|
-
|
129
|
+
newdata = self.to_dict()["exceptions"]
|
130
|
+
return data_to_html(newdata, replace_new_lines=True)
|
131
131
|
|
132
132
|
def show_exceptions(self, tracebacks=False):
|
133
133
|
"""Print the exceptions."""
|
@@ -257,6 +257,8 @@ class TaskHistory:
|
|
257
257
|
for question_name, exceptions in interview.exceptions.items():
|
258
258
|
for exception in exceptions:
|
259
259
|
exception_type = exception.exception.__class__.__name__
|
260
|
+
# exception_type = exception["exception"]
|
261
|
+
# breakpoint()
|
260
262
|
if exception_type in exceptions_by_type:
|
261
263
|
exceptions_by_type[exception_type] += 1
|
262
264
|
else:
|
@@ -343,9 +345,9 @@ class TaskHistory:
|
|
343
345
|
|
344
346
|
env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
|
345
347
|
|
346
|
-
#
|
347
|
-
|
348
|
+
# Load and render a template
|
348
349
|
template = env.get_template("base.html")
|
350
|
+
# rendered_template = template.render(your_data=your_data)
|
349
351
|
|
350
352
|
# Render the template with data
|
351
353
|
output = template.render(
|
@@ -359,7 +361,6 @@ class TaskHistory:
|
|
359
361
|
exceptions_by_model=self.exceptions_by_model,
|
360
362
|
exceptions_by_service=self.exceptions_by_service,
|
361
363
|
models_used=models_used,
|
362
|
-
max_interviews=self.max_interviews,
|
363
364
|
)
|
364
365
|
return output
|
365
366
|
|
@@ -369,7 +370,7 @@ class TaskHistory:
|
|
369
370
|
return_link=False,
|
370
371
|
css=None,
|
371
372
|
cta="Open Report in New Tab",
|
372
|
-
open_in_browser=
|
373
|
+
open_in_browser=True,
|
373
374
|
):
|
374
375
|
"""Return an HTML report."""
|
375
376
|
|