edsl 0.1.33__py3-none-any.whl → 0.1.33.dev1__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 +3 -9
- edsl/__init__.py +3 -8
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +8 -40
- edsl/agents/AgentList.py +0 -43
- edsl/agents/Invigilator.py +219 -135
- edsl/agents/InvigilatorBase.py +59 -148
- edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +89 -138
- edsl/agents/__init__.py +0 -1
- edsl/config.py +56 -47
- edsl/coop/coop.py +7 -50
- edsl/data/Cache.py +1 -35
- edsl/data_transfer_models.py +38 -73
- edsl/enums.py +0 -4
- edsl/exceptions/language_models.py +1 -25
- edsl/exceptions/questions.py +5 -62
- edsl/exceptions/results.py +0 -4
- edsl/inference_services/AnthropicService.py +11 -13
- edsl/inference_services/AwsBedrock.py +17 -19
- edsl/inference_services/AzureAI.py +20 -37
- edsl/inference_services/GoogleService.py +12 -16
- edsl/inference_services/GroqService.py +0 -2
- edsl/inference_services/InferenceServiceABC.py +3 -58
- edsl/inference_services/OpenAIService.py +54 -48
- edsl/inference_services/models_available_cache.py +6 -0
- edsl/inference_services/registry.py +0 -6
- edsl/jobs/Answers.py +12 -10
- edsl/jobs/Jobs.py +21 -36
- edsl/jobs/buckets/BucketCollection.py +15 -24
- edsl/jobs/buckets/TokenBucket.py +14 -93
- edsl/jobs/interviews/Interview.py +78 -366
- edsl/jobs/interviews/InterviewExceptionEntry.py +19 -85
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +286 -0
- edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +68 -14
- edsl/jobs/interviews/retry_management.py +37 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +175 -146
- edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +23 -30
- edsl/jobs/tasks/TaskHistory.py +213 -148
- edsl/language_models/LanguageModel.py +156 -261
- edsl/language_models/ModelList.py +2 -2
- edsl/language_models/RegisterLanguageModelsMeta.py +29 -14
- edsl/language_models/registry.py +6 -23
- edsl/language_models/repair.py +19 -0
- edsl/prompts/Prompt.py +2 -52
- edsl/questions/AnswerValidatorMixin.py +26 -23
- edsl/questions/QuestionBase.py +249 -329
- edsl/questions/QuestionBudget.py +41 -99
- edsl/questions/QuestionCheckBox.py +35 -227
- edsl/questions/QuestionExtract.py +27 -98
- edsl/questions/QuestionFreeText.py +29 -52
- edsl/questions/QuestionFunctional.py +0 -7
- edsl/questions/QuestionList.py +22 -141
- edsl/questions/QuestionMultipleChoice.py +65 -159
- edsl/questions/QuestionNumerical.py +46 -88
- edsl/questions/QuestionRank.py +24 -182
- edsl/questions/RegisterQuestionsMeta.py +12 -31
- edsl/questions/__init__.py +4 -3
- edsl/questions/derived/QuestionLikertFive.py +5 -10
- edsl/questions/derived/QuestionLinearScale.py +2 -15
- edsl/questions/derived/QuestionTopK.py +1 -10
- edsl/questions/derived/QuestionYesNo.py +3 -24
- edsl/questions/descriptors.py +7 -43
- edsl/questions/question_registry.py +2 -6
- edsl/results/Dataset.py +0 -20
- edsl/results/DatasetExportMixin.py +48 -46
- edsl/results/Result.py +5 -32
- edsl/results/Results.py +46 -135
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/scenarios/FileStore.py +10 -71
- edsl/scenarios/Scenario.py +25 -96
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +39 -361
- edsl/scenarios/ScenarioListExportMixin.py +0 -9
- edsl/scenarios/ScenarioListPdfMixin.py +4 -150
- edsl/study/SnapShot.py +1 -8
- edsl/study/Study.py +0 -32
- edsl/surveys/Rule.py +1 -10
- edsl/surveys/RuleCollection.py +5 -21
- edsl/surveys/Survey.py +310 -636
- edsl/surveys/SurveyExportMixin.py +9 -71
- edsl/surveys/SurveyFlowVisualizationMixin.py +1 -2
- edsl/surveys/SurveyQualtricsImport.py +4 -75
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/utilities.py +1 -9
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/METADATA +2 -5
- edsl-0.1.33.dev1.dist-info/RECORD +209 -0
- edsl/TemplateLoader.py +0 -24
- edsl/auto/AutoStudy.py +0 -117
- edsl/auto/StageBase.py +0 -230
- edsl/auto/StageGenerateSurvey.py +0 -178
- edsl/auto/StageLabelQuestions.py +0 -125
- edsl/auto/StagePersona.py +0 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
- edsl/auto/StagePersonaDimensionValues.py +0 -74
- edsl/auto/StagePersonaDimensions.py +0 -69
- edsl/auto/StageQuestions.py +0 -73
- edsl/auto/SurveyCreatorPipeline.py +0 -21
- edsl/auto/utilities.py +0 -224
- edsl/coop/PriceFetcher.py +0 -58
- edsl/inference_services/MistralAIService.py +0 -120
- edsl/inference_services/TestService.py +0 -80
- edsl/inference_services/TogetherAIService.py +0 -170
- edsl/jobs/FailedQuestion.py +0 -78
- edsl/jobs/runners/JobsRunnerStatus.py +0 -331
- edsl/language_models/fake_openai_call.py +0 -15
- edsl/language_models/fake_openai_service.py +0 -61
- edsl/language_models/utilities.py +0 -61
- edsl/questions/QuestionBaseGenMixin.py +0 -133
- edsl/questions/QuestionBasePromptsMixin.py +0 -266
- edsl/questions/Quick.py +0 -41
- edsl/questions/ResponseValidatorABC.py +0 -170
- edsl/questions/decorators.py +0 -21
- edsl/questions/prompt_templates/question_budget.jinja +0 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +0 -32
- edsl/questions/prompt_templates/question_extract.jinja +0 -11
- edsl/questions/prompt_templates/question_free_text.jinja +0 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +0 -11
- edsl/questions/prompt_templates/question_list.jinja +0 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +0 -33
- edsl/questions/prompt_templates/question_numerical.jinja +0 -37
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +0 -7
- edsl/questions/templates/budget/question_presentation.jinja +0 -7
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +0 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +0 -22
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +0 -7
- edsl/questions/templates/extract/question_presentation.jinja +0 -1
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +0 -1
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +0 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +0 -12
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +0 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +0 -5
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +0 -4
- edsl/questions/templates/list/question_presentation.jinja +0 -5
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +0 -9
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +0 -12
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +0 -8
- edsl/questions/templates/numerical/question_presentation.jinja +0 -7
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +0 -11
- edsl/questions/templates/rank/question_presentation.jinja +0 -15
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +0 -8
- edsl/questions/templates/top_k/question_presentation.jinja +0 -22
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +0 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +0 -12
- edsl/results/DatasetTree.py +0 -145
- edsl/results/Selector.py +0 -118
- edsl/results/tree_explore.py +0 -115
- edsl/surveys/instructions/ChangeInstruction.py +0 -47
- edsl/surveys/instructions/Instruction.py +0 -34
- edsl/surveys/instructions/InstructionCollection.py +0 -77
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +0 -24
- edsl/templates/error_reporting/exceptions_by_model.html +0 -35
- edsl/templates/error_reporting/exceptions_by_question_name.html +0 -17
- edsl/templates/error_reporting/exceptions_by_type.html +0 -17
- edsl/templates/error_reporting/interview_details.html +0 -116
- edsl/templates/error_reporting/interviews.html +0 -10
- edsl/templates/error_reporting/overview.html +0 -5
- edsl/templates/error_reporting/performance_plot.html +0 -2
- edsl/templates/error_reporting/report.css +0 -74
- edsl/templates/error_reporting/report.html +0 -118
- edsl/templates/error_reporting/report.js +0 -25
- edsl-0.1.33.dist-info/RECORD +0 -295
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/WHEEL +0 -0
@@ -1,27 +1,42 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import time
|
3
|
-
import math
|
4
3
|
import asyncio
|
5
|
-
import
|
6
|
-
import threading
|
7
|
-
from typing import Coroutine, List, AsyncGenerator, Optional, Union, Generator
|
4
|
+
import time
|
8
5
|
from contextlib import contextmanager
|
9
|
-
from collections import UserList
|
10
6
|
|
11
|
-
from
|
12
|
-
from rich.live import Live
|
13
|
-
from rich.console import Console
|
7
|
+
from typing import Coroutine, List, AsyncGenerator, Optional, Union
|
14
8
|
|
15
9
|
from edsl import shared_globals
|
16
10
|
from edsl.jobs.interviews.Interview import Interview
|
17
|
-
from edsl.jobs.runners.
|
18
|
-
|
11
|
+
from edsl.jobs.runners.JobsRunnerStatusMixin import JobsRunnerStatusMixin
|
19
12
|
from edsl.jobs.tasks.TaskHistory import TaskHistory
|
20
13
|
from edsl.jobs.buckets.BucketCollection import BucketCollection
|
21
14
|
from edsl.utilities.decorators import jupyter_nb_handler
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
|
16
|
+
import time
|
17
|
+
import functools
|
18
|
+
|
19
|
+
|
20
|
+
def cache_with_timeout(timeout):
|
21
|
+
def decorator(func):
|
22
|
+
cached_result = {}
|
23
|
+
last_computation_time = [0] # Using list to store mutable value
|
24
|
+
|
25
|
+
@functools.wraps(func)
|
26
|
+
def wrapper(*args, **kwargs):
|
27
|
+
current_time = time.time()
|
28
|
+
if (current_time - last_computation_time[0]) >= timeout:
|
29
|
+
cached_result["value"] = func(*args, **kwargs)
|
30
|
+
last_computation_time[0] = current_time
|
31
|
+
return cached_result["value"]
|
32
|
+
|
33
|
+
return wrapper
|
34
|
+
|
35
|
+
return decorator
|
36
|
+
|
37
|
+
|
38
|
+
# from queue import Queue
|
39
|
+
from collections import UserList
|
25
40
|
|
26
41
|
|
27
42
|
class StatusTracker(UserList):
|
@@ -33,87 +48,99 @@ class StatusTracker(UserList):
|
|
33
48
|
return print(f"Completed: {len(self.data)} of {self.total_tasks}", end="\r")
|
34
49
|
|
35
50
|
|
36
|
-
class JobsRunnerAsyncio:
|
51
|
+
class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
37
52
|
"""A class for running a collection of interviews asynchronously.
|
38
53
|
|
39
54
|
It gets instaniated from a Jobs object.
|
40
55
|
The Jobs object is a collection of interviews that are to be run.
|
41
56
|
"""
|
42
57
|
|
43
|
-
def __init__(self, jobs:
|
58
|
+
def __init__(self, jobs: Jobs):
|
44
59
|
self.jobs = jobs
|
60
|
+
# this creates the interviews, which can take a while
|
45
61
|
self.interviews: List["Interview"] = jobs.interviews()
|
46
62
|
self.bucket_collection: "BucketCollection" = jobs.bucket_collection
|
47
63
|
self.total_interviews: List["Interview"] = []
|
48
64
|
|
49
|
-
# self.jobs_runner_status = JobsRunnerStatus(self, n=1)
|
50
|
-
|
51
65
|
async def run_async_generator(
|
52
66
|
self,
|
53
67
|
cache: "Cache",
|
54
68
|
n: int = 1,
|
69
|
+
debug: bool = False,
|
55
70
|
stop_on_exception: bool = False,
|
56
|
-
sidecar_model:
|
71
|
+
sidecar_model: "LanguageModel" = None,
|
57
72
|
total_interviews: Optional[List["Interview"]] = None,
|
58
|
-
raise_validation_errors: bool = False,
|
59
73
|
) -> AsyncGenerator["Result", None]:
|
60
74
|
"""Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
61
75
|
|
62
76
|
Completed tasks are yielded as they are completed.
|
63
77
|
|
64
78
|
:param n: how many times to run each interview
|
79
|
+
:param debug:
|
65
80
|
:param stop_on_exception: Whether to stop the interview if an exception is raised
|
66
81
|
:param sidecar_model: a language model to use in addition to the interview's model
|
67
82
|
:param total_interviews: A list of interviews to run can be provided instead.
|
68
|
-
:param raise_validation_errors: Whether to raise validation errors
|
69
83
|
"""
|
70
84
|
tasks = []
|
71
|
-
if total_interviews:
|
85
|
+
if total_interviews:
|
72
86
|
self.total_interviews = total_interviews
|
73
87
|
else:
|
74
|
-
self.
|
75
|
-
|
88
|
+
self._populate_total_interviews(
|
89
|
+
n=n
|
76
90
|
) # Populate self.total_interviews before creating tasks
|
77
91
|
|
78
92
|
for interview in self.total_interviews:
|
79
93
|
interviewing_task = self._build_interview_task(
|
80
94
|
interview=interview,
|
95
|
+
debug=debug,
|
81
96
|
stop_on_exception=stop_on_exception,
|
82
97
|
sidecar_model=sidecar_model,
|
83
|
-
raise_validation_errors=raise_validation_errors,
|
84
98
|
)
|
85
99
|
tasks.append(asyncio.create_task(interviewing_task))
|
86
100
|
|
87
101
|
for task in asyncio.as_completed(tasks):
|
88
102
|
result = await task
|
89
|
-
self.jobs_runner_status.add_completed_interview(result)
|
90
103
|
yield result
|
91
104
|
|
92
|
-
def _populate_total_interviews(
|
93
|
-
self, n: int = 1
|
94
|
-
) -> Generator["Interview", None, None]:
|
105
|
+
def _populate_total_interviews(self, n: int = 1) -> None:
|
95
106
|
"""Populates self.total_interviews with n copies of each interview.
|
96
107
|
|
97
108
|
:param n: how many times to run each interview.
|
98
109
|
"""
|
110
|
+
# TODO: Why not return a list of interviews instead of modifying the object?
|
111
|
+
|
112
|
+
self.total_interviews = []
|
99
113
|
for interview in self.interviews:
|
100
114
|
for iteration in range(n):
|
101
115
|
if iteration > 0:
|
102
|
-
|
116
|
+
new_interview = interview.duplicate(
|
117
|
+
iteration=iteration, cache=self.cache
|
118
|
+
)
|
119
|
+
self.total_interviews.append(new_interview)
|
103
120
|
else:
|
104
|
-
interview.cache =
|
105
|
-
|
121
|
+
interview.cache = (
|
122
|
+
self.cache
|
123
|
+
) # set the cache for the first interview
|
124
|
+
self.total_interviews.append(interview)
|
125
|
+
|
126
|
+
async def run_async(self, cache=None, n=1) -> Results:
|
127
|
+
from edsl.results.Results import Results
|
106
128
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
129
|
+
# breakpoint()
|
130
|
+
# tracker = StatusTracker(total_tasks=len(self.interviews))
|
131
|
+
|
132
|
+
if cache is None:
|
133
|
+
self.cache = Cache()
|
134
|
+
else:
|
135
|
+
self.cache = cache
|
111
136
|
data = []
|
112
137
|
async for result in self.run_async_generator(cache=self.cache, n=n):
|
113
138
|
data.append(result)
|
114
139
|
return Results(survey=self.jobs.survey, data=data)
|
115
140
|
|
116
141
|
def simple_run(self):
|
142
|
+
from edsl.results.Results import Results
|
143
|
+
|
117
144
|
data = asyncio.run(self.run_async())
|
118
145
|
return Results(survey=self.jobs.survey, data=data)
|
119
146
|
|
@@ -121,13 +148,14 @@ class JobsRunnerAsyncio:
|
|
121
148
|
self,
|
122
149
|
*,
|
123
150
|
interview: Interview,
|
151
|
+
debug: bool,
|
124
152
|
stop_on_exception: bool = False,
|
125
|
-
sidecar_model: Optional[
|
126
|
-
|
127
|
-
) -> "Result":
|
153
|
+
sidecar_model: Optional[LanguageModel] = None,
|
154
|
+
) -> Result:
|
128
155
|
"""Conducts an interview and returns the result.
|
129
156
|
|
130
157
|
:param interview: the interview to conduct
|
158
|
+
:param debug: prints debug messages
|
131
159
|
:param stop_on_exception: stops the interview if an exception is raised
|
132
160
|
:param sidecar_model: a language model to use in addition to the interview's model
|
133
161
|
"""
|
@@ -136,37 +164,24 @@ class JobsRunnerAsyncio:
|
|
136
164
|
|
137
165
|
# get the results of the interview
|
138
166
|
answer, valid_results = await interview.async_conduct_interview(
|
167
|
+
debug=debug,
|
139
168
|
model_buckets=model_buckets,
|
140
169
|
stop_on_exception=stop_on_exception,
|
141
170
|
sidecar_model=sidecar_model,
|
142
|
-
raise_validation_errors=raise_validation_errors,
|
143
171
|
)
|
144
172
|
|
145
|
-
question_results = {}
|
146
|
-
for result in valid_results:
|
147
|
-
question_results[result.question_name] = result
|
148
|
-
|
149
|
-
answer_key_names = list(question_results.keys())
|
150
|
-
|
151
|
-
generated_tokens_dict = {
|
152
|
-
k + "_generated_tokens": question_results[k].generated_tokens
|
153
|
-
for k in answer_key_names
|
154
|
-
}
|
155
|
-
comments_dict = {
|
156
|
-
k + "_comment": question_results[k].comment for k in answer_key_names
|
157
|
-
}
|
158
|
-
|
159
173
|
# we should have a valid result for each question
|
160
|
-
|
174
|
+
answer_key_names = {k for k in set(answer.keys()) if not k.endswith("_comment")}
|
175
|
+
|
161
176
|
assert len(valid_results) == len(answer_key_names)
|
162
177
|
|
163
178
|
# TODO: move this down into Interview
|
164
179
|
question_name_to_prompts = dict({})
|
165
180
|
for result in valid_results:
|
166
|
-
question_name = result
|
181
|
+
question_name = result["question_name"]
|
167
182
|
question_name_to_prompts[question_name] = {
|
168
|
-
"user_prompt": result
|
169
|
-
"system_prompt": result
|
183
|
+
"user_prompt": result["prompts"]["user_prompt"],
|
184
|
+
"system_prompt": result["prompts"]["system_prompt"],
|
170
185
|
}
|
171
186
|
|
172
187
|
prompt_dictionary = {}
|
@@ -180,31 +195,22 @@ class JobsRunnerAsyncio:
|
|
180
195
|
|
181
196
|
raw_model_results_dictionary = {}
|
182
197
|
for result in valid_results:
|
183
|
-
question_name = result
|
198
|
+
question_name = result["question_name"]
|
184
199
|
raw_model_results_dictionary[
|
185
200
|
question_name + "_raw_model_response"
|
186
|
-
] = result
|
187
|
-
|
188
|
-
|
189
|
-
"NA"
|
190
|
-
if isinstance(result.cost, str)
|
191
|
-
or result.cost == 0
|
192
|
-
or result.cost is None
|
193
|
-
else 1.0 / result.cost
|
194
|
-
)
|
195
|
-
raw_model_results_dictionary[question_name + "_one_usd_buys"] = one_use_buys
|
201
|
+
] = result["raw_model_response"]
|
202
|
+
|
203
|
+
from edsl.results.Result import Result
|
196
204
|
|
197
205
|
result = Result(
|
198
206
|
agent=interview.agent,
|
199
207
|
scenario=interview.scenario,
|
200
208
|
model=interview.model,
|
201
209
|
iteration=interview.iteration,
|
202
|
-
answer=
|
210
|
+
answer=answer,
|
203
211
|
prompt=prompt_dictionary,
|
204
212
|
raw_model_response=raw_model_results_dictionary,
|
205
213
|
survey=interview.survey,
|
206
|
-
generated_tokens=generated_tokens_dict,
|
207
|
-
comments_dict=comments_dict,
|
208
214
|
)
|
209
215
|
result.interview_hash = hash(interview)
|
210
216
|
|
@@ -214,109 +220,132 @@ class JobsRunnerAsyncio:
|
|
214
220
|
def elapsed_time(self):
|
215
221
|
return time.monotonic() - self.start_time
|
216
222
|
|
217
|
-
def process_results(
|
218
|
-
self, raw_results: Results, cache: Cache, print_exceptions: bool
|
219
|
-
):
|
220
|
-
interview_lookup = {
|
221
|
-
hash(interview): index
|
222
|
-
for index, interview in enumerate(self.total_interviews)
|
223
|
-
}
|
224
|
-
interview_hashes = list(interview_lookup.keys())
|
225
|
-
|
226
|
-
results = Results(
|
227
|
-
survey=self.jobs.survey,
|
228
|
-
data=sorted(
|
229
|
-
raw_results, key=lambda x: interview_hashes.index(x.interview_hash)
|
230
|
-
),
|
231
|
-
)
|
232
|
-
results.cache = cache
|
233
|
-
results.task_history = TaskHistory(
|
234
|
-
self.total_interviews, include_traceback=False
|
235
|
-
)
|
236
|
-
results.has_unfixed_exceptions = results.task_history.has_unfixed_exceptions
|
237
|
-
results.bucket_collection = self.bucket_collection
|
238
|
-
|
239
|
-
if results.has_unfixed_exceptions and print_exceptions:
|
240
|
-
from edsl.scenarios.FileStore import HTMLFileStore
|
241
|
-
from edsl.config import CONFIG
|
242
|
-
from edsl.coop.coop import Coop
|
243
|
-
|
244
|
-
msg = f"Exceptions were raised in {len(results.task_history.indices)} out of {len(self.total_interviews)} interviews.\n"
|
245
|
-
|
246
|
-
if len(results.task_history.indices) > 5:
|
247
|
-
msg += f"Exceptions were raised in the following interviews: {results.task_history.indices}.\n"
|
248
|
-
|
249
|
-
print(msg)
|
250
|
-
# this is where exceptions are opening up
|
251
|
-
filepath = results.task_history.html(
|
252
|
-
cta="Open report to see details.",
|
253
|
-
open_in_browser=True,
|
254
|
-
return_link=True,
|
255
|
-
)
|
256
|
-
|
257
|
-
try:
|
258
|
-
coop = Coop()
|
259
|
-
user_edsl_settings = coop.edsl_settings
|
260
|
-
remote_logging = user_edsl_settings["remote_logging"]
|
261
|
-
except Exception as e:
|
262
|
-
print(e)
|
263
|
-
remote_logging = False
|
264
|
-
if remote_logging:
|
265
|
-
filestore = HTMLFileStore(filepath)
|
266
|
-
coop_details = filestore.push(description="Error report")
|
267
|
-
print(coop_details)
|
268
|
-
|
269
|
-
print("Also see: https://docs.expectedparrot.com/en/latest/exceptions.html")
|
270
|
-
|
271
|
-
return results
|
272
|
-
|
273
223
|
@jupyter_nb_handler
|
274
224
|
async def run(
|
275
225
|
self,
|
276
226
|
cache: Union[Cache, False, None],
|
277
227
|
n: int = 1,
|
228
|
+
debug: bool = False,
|
278
229
|
stop_on_exception: bool = False,
|
279
230
|
progress_bar: bool = False,
|
280
231
|
sidecar_model: Optional[LanguageModel] = None,
|
281
232
|
print_exceptions: bool = True,
|
282
|
-
raise_validation_errors: bool = False,
|
283
233
|
) -> "Coroutine":
|
284
234
|
"""Runs a collection of interviews, handling both async and sync contexts."""
|
235
|
+
from rich.console import Console
|
285
236
|
|
237
|
+
console = Console()
|
286
238
|
self.results = []
|
287
239
|
self.start_time = time.monotonic()
|
288
240
|
self.completed = False
|
289
241
|
self.cache = cache
|
290
242
|
self.sidecar_model = sidecar_model
|
291
243
|
|
292
|
-
|
244
|
+
from edsl.results.Results import Results
|
245
|
+
from rich.live import Live
|
246
|
+
from rich.console import Console
|
247
|
+
|
248
|
+
@cache_with_timeout(1)
|
249
|
+
def generate_table():
|
250
|
+
return self.status_table(self.results, self.elapsed_time)
|
293
251
|
|
294
|
-
async def process_results(cache):
|
252
|
+
async def process_results(cache, progress_bar_context=None):
|
295
253
|
"""Processes results from interviews."""
|
296
254
|
async for result in self.run_async_generator(
|
297
255
|
n=n,
|
256
|
+
debug=debug,
|
298
257
|
stop_on_exception=stop_on_exception,
|
299
258
|
cache=cache,
|
300
259
|
sidecar_model=sidecar_model,
|
301
|
-
raise_validation_errors=raise_validation_errors,
|
302
260
|
):
|
303
261
|
self.results.append(result)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
262
|
+
if progress_bar_context:
|
263
|
+
progress_bar_context.update(generate_table())
|
264
|
+
self.completed = True
|
265
|
+
|
266
|
+
async def update_progress_bar(progress_bar_context):
|
267
|
+
"""Updates the progress bar at fixed intervals."""
|
268
|
+
if progress_bar_context is None:
|
269
|
+
return
|
270
|
+
|
271
|
+
while True:
|
272
|
+
progress_bar_context.update(generate_table())
|
273
|
+
await asyncio.sleep(0.1) # Update interval
|
274
|
+
if self.completed:
|
275
|
+
break
|
276
|
+
|
277
|
+
@contextmanager
|
278
|
+
def conditional_context(condition, context_manager):
|
279
|
+
if condition:
|
280
|
+
with context_manager as cm:
|
281
|
+
yield cm
|
282
|
+
else:
|
283
|
+
yield
|
284
|
+
|
285
|
+
with conditional_context(
|
286
|
+
progress_bar, Live(generate_table(), console=console, refresh_per_second=1)
|
287
|
+
) as progress_bar_context:
|
288
|
+
with cache as c:
|
289
|
+
progress_task = asyncio.create_task(
|
290
|
+
update_progress_bar(progress_bar_context)
|
291
|
+
)
|
292
|
+
|
293
|
+
try:
|
294
|
+
await asyncio.gather(
|
295
|
+
progress_task,
|
296
|
+
process_results(
|
297
|
+
cache=c, progress_bar_context=progress_bar_context
|
298
|
+
),
|
299
|
+
)
|
300
|
+
except asyncio.CancelledError:
|
301
|
+
pass
|
302
|
+
finally:
|
303
|
+
progress_task.cancel() # Cancel the progress_task when process_results is done
|
304
|
+
await progress_task
|
305
|
+
|
306
|
+
await asyncio.sleep(1) # short delay to show the final status
|
307
|
+
|
308
|
+
if progress_bar_context:
|
309
|
+
progress_bar_context.update(generate_table())
|
310
|
+
|
311
|
+
# puts results in the same order as the total interviews
|
312
|
+
interview_hashes = [hash(interview) for interview in self.total_interviews]
|
313
|
+
self.results = sorted(
|
314
|
+
self.results, key=lambda x: interview_hashes.index(x.interview_hash)
|
315
|
+
)
|
309
316
|
|
310
|
-
|
311
|
-
|
312
|
-
|
317
|
+
results = Results(survey=self.jobs.survey, data=self.results)
|
318
|
+
task_history = TaskHistory(self.total_interviews, include_traceback=False)
|
319
|
+
results.task_history = task_history
|
320
|
+
|
321
|
+
results.has_exceptions = task_history.has_exceptions
|
322
|
+
|
323
|
+
if results.has_exceptions:
|
324
|
+
# put the failed interviews in the results object as a list
|
325
|
+
failed_interviews = [
|
326
|
+
interview.duplicate(
|
327
|
+
iteration=interview.iteration, cache=interview.cache
|
328
|
+
)
|
329
|
+
for interview in self.total_interviews
|
330
|
+
if interview.has_exceptions
|
331
|
+
]
|
332
|
+
from edsl.jobs.Jobs import Jobs
|
333
|
+
|
334
|
+
results.failed_jobs = Jobs.from_interviews(
|
335
|
+
[interview for interview in failed_interviews]
|
336
|
+
)
|
337
|
+
if print_exceptions:
|
338
|
+
msg = f"Exceptions were raised in {len(results.task_history.indices)} out of {len(self.total_interviews)} interviews.\n"
|
313
339
|
|
314
|
-
|
315
|
-
|
340
|
+
if len(results.task_history.indices) > 5:
|
341
|
+
msg += f"Exceptions were raised in the following interviews: {results.task_history.indices}.\n"
|
316
342
|
|
317
|
-
|
318
|
-
|
343
|
+
shared_globals["edsl_runner_exceptions"] = task_history
|
344
|
+
print(msg)
|
345
|
+
# this is where exceptions are opening up
|
346
|
+
task_history.html(cta="Open report to see details.")
|
347
|
+
print(
|
348
|
+
"Also see: https://docs.expectedparrot.com/en/latest/exceptions.html"
|
349
|
+
)
|
319
350
|
|
320
|
-
return
|
321
|
-
raw_results=self.results, cache=cache, print_exceptions=print_exceptions
|
322
|
-
)
|
351
|
+
return results
|