edsl 0.1.29.dev6__py3-none-any.whl → 0.1.30__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 +6 -3
- edsl/__init__.py +23 -23
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +43 -40
- edsl/agents/AgentList.py +23 -22
- edsl/agents/Invigilator.py +19 -2
- edsl/agents/descriptors.py +2 -1
- edsl/base/Base.py +289 -0
- edsl/config.py +2 -1
- edsl/conversation/car_buying.py +1 -1
- edsl/coop/utils.py +28 -1
- edsl/data/Cache.py +41 -18
- edsl/data/CacheEntry.py +6 -7
- edsl/data/SQLiteDict.py +11 -3
- edsl/data_transfer_models.py +4 -0
- edsl/jobs/Answers.py +15 -1
- edsl/jobs/Jobs.py +86 -33
- edsl/jobs/buckets/ModelBuckets.py +14 -2
- edsl/jobs/buckets/TokenBucket.py +32 -5
- edsl/jobs/interviews/Interview.py +99 -79
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +18 -24
- edsl/jobs/runners/JobsRunnerAsyncio.py +16 -16
- edsl/jobs/tasks/QuestionTaskCreator.py +10 -6
- edsl/jobs/tasks/TaskHistory.py +4 -3
- edsl/language_models/LanguageModel.py +17 -17
- edsl/language_models/ModelList.py +1 -1
- edsl/language_models/repair.py +8 -7
- edsl/notebooks/Notebook.py +16 -10
- edsl/questions/QuestionBase.py +6 -2
- edsl/questions/QuestionBudget.py +5 -6
- edsl/questions/QuestionCheckBox.py +7 -3
- edsl/questions/QuestionExtract.py +5 -3
- edsl/questions/QuestionFreeText.py +7 -5
- edsl/questions/QuestionFunctional.py +34 -5
- edsl/questions/QuestionList.py +3 -4
- edsl/questions/QuestionMultipleChoice.py +68 -12
- edsl/questions/QuestionNumerical.py +4 -3
- edsl/questions/QuestionRank.py +5 -3
- edsl/questions/__init__.py +4 -3
- edsl/questions/descriptors.py +46 -4
- edsl/results/DatasetExportMixin.py +570 -0
- edsl/results/Result.py +66 -70
- edsl/results/Results.py +160 -68
- edsl/results/ResultsDBMixin.py +7 -3
- edsl/results/ResultsExportMixin.py +22 -537
- edsl/results/ResultsGGMixin.py +3 -3
- edsl/results/ResultsToolsMixin.py +1 -4
- edsl/scenarios/FileStore.py +299 -0
- edsl/scenarios/Scenario.py +16 -24
- edsl/scenarios/ScenarioList.py +25 -14
- edsl/scenarios/ScenarioListExportMixin.py +32 -0
- edsl/scenarios/ScenarioListPdfMixin.py +2 -1
- edsl/scenarios/__init__.py +1 -0
- edsl/study/Study.py +5 -7
- edsl/surveys/MemoryPlan.py +11 -4
- edsl/surveys/Survey.py +52 -15
- edsl/surveys/SurveyExportMixin.py +4 -2
- edsl/surveys/SurveyFlowVisualizationMixin.py +6 -4
- edsl/utilities/__init__.py +21 -21
- edsl/utilities/interface.py +66 -45
- edsl/utilities/utilities.py +11 -13
- {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/METADATA +1 -1
- {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/RECORD +65 -61
- {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/WHEEL +1 -1
- {edsl-0.1.29.dev6.dist-info → edsl-0.1.30.dist-info}/LICENSE +0 -0
@@ -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
|
|
@@ -23,7 +25,7 @@ TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
|
|
23
25
|
class InterviewTaskBuildingMixin:
|
24
26
|
def _build_invigilators(
|
25
27
|
self, debug: bool
|
26
|
-
) -> Generator[InvigilatorBase, None, None]:
|
28
|
+
) -> Generator["InvigilatorBase", None, None]:
|
27
29
|
"""Create an invigilator for each question.
|
28
30
|
|
29
31
|
:param debug: whether to use debug mode, in which case `InvigilatorDebug` is used.
|
@@ -33,7 +35,7 @@ class InterviewTaskBuildingMixin:
|
|
33
35
|
for question in self.survey.questions:
|
34
36
|
yield self._get_invigilator(question=question, debug=debug)
|
35
37
|
|
36
|
-
def _get_invigilator(self, question: QuestionBase, debug: bool) -> "Invigilator":
|
38
|
+
def _get_invigilator(self, question: "QuestionBase", debug: bool) -> "Invigilator":
|
37
39
|
"""Return an invigilator for the given question.
|
38
40
|
|
39
41
|
:param question: the question to be answered
|
@@ -82,7 +84,7 @@ class InterviewTaskBuildingMixin:
|
|
82
84
|
return tuple(tasks) # , invigilators
|
83
85
|
|
84
86
|
def _get_tasks_that_must_be_completed_before(
|
85
|
-
self, *, tasks: list[asyncio.Task], question: QuestionBase
|
87
|
+
self, *, tasks: list[asyncio.Task], question: "QuestionBase"
|
86
88
|
) -> Generator[asyncio.Task, None, None]:
|
87
89
|
"""Return the tasks that must be completed before the given question can be answered.
|
88
90
|
|
@@ -98,7 +100,7 @@ class InterviewTaskBuildingMixin:
|
|
98
100
|
def _create_question_task(
|
99
101
|
self,
|
100
102
|
*,
|
101
|
-
question: QuestionBase,
|
103
|
+
question: "QuestionBase",
|
102
104
|
tasks_that_must_be_completed_before: list[asyncio.Task],
|
103
105
|
model_buckets: ModelBuckets,
|
104
106
|
debug: bool,
|
@@ -150,15 +152,17 @@ class InterviewTaskBuildingMixin:
|
|
150
152
|
async def _answer_question_and_record_task(
|
151
153
|
self,
|
152
154
|
*,
|
153
|
-
question: QuestionBase,
|
155
|
+
question: "QuestionBase",
|
154
156
|
debug: bool,
|
155
157
|
task=None,
|
156
|
-
) -> AgentResponseDict:
|
158
|
+
) -> "AgentResponseDict":
|
157
159
|
"""Answer a question and records the task.
|
158
160
|
|
159
161
|
This in turn calls the the passed-in agent's async_answer_question method, which returns a response dictionary.
|
160
162
|
Note that is updates answers dictionary with the response.
|
161
163
|
"""
|
164
|
+
from edsl.data_transfer_models import AgentResponseDict
|
165
|
+
|
162
166
|
try:
|
163
167
|
invigilator = self._get_invigilator(question, debug=debug)
|
164
168
|
|
@@ -171,24 +175,14 @@ class InterviewTaskBuildingMixin:
|
|
171
175
|
|
172
176
|
self._add_answer(response=response, question=question)
|
173
177
|
|
174
|
-
# With the answer to the question, we can now cancel any skipped questions
|
175
178
|
self._cancel_skipped_questions(question)
|
176
179
|
return AgentResponseDict(**response)
|
177
180
|
except Exception as e:
|
178
181
|
raise e
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
# # Extract and print the traceback info
|
184
|
-
# tb = e.__traceback__
|
185
|
-
# while tb is not None:
|
186
|
-
# print(f"File {tb.tb_frame.f_code.co_filename}, line {tb.tb_lineno}, in {tb.tb_frame.f_code.co_name}")
|
187
|
-
# tb = tb.tb_next
|
188
|
-
# breakpoint()
|
189
|
-
# raise e
|
190
|
-
|
191
|
-
def _add_answer(self, response: AgentResponseDict, question: QuestionBase) -> None:
|
182
|
+
|
183
|
+
def _add_answer(
|
184
|
+
self, response: "AgentResponseDict", question: "QuestionBase"
|
185
|
+
) -> None:
|
192
186
|
"""Add the answer to the answers dictionary.
|
193
187
|
|
194
188
|
:param response: the response to the question.
|
@@ -196,7 +190,7 @@ class InterviewTaskBuildingMixin:
|
|
196
190
|
"""
|
197
191
|
self.answers.add_answer(response=response, question=question)
|
198
192
|
|
199
|
-
def _skip_this_question(self, current_question: QuestionBase) -> bool:
|
193
|
+
def _skip_this_question(self, current_question: "QuestionBase") -> bool:
|
200
194
|
"""Determine if the current question should be skipped.
|
201
195
|
|
202
196
|
:param current_question: the question to be answered.
|
@@ -1,29 +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
|
-
|
18
|
-
# from edsl.jobs.Jobs import Jobs
|
19
11
|
from edsl.jobs.runners.JobsRunnerStatusMixin import JobsRunnerStatusMixin
|
20
|
-
from edsl.language_models import LanguageModel
|
21
|
-
from edsl.data.Cache import Cache
|
22
|
-
|
23
12
|
from edsl.jobs.tasks.TaskHistory import TaskHistory
|
24
13
|
from edsl.jobs.buckets.BucketCollection import BucketCollection
|
25
|
-
|
26
|
-
import time
|
14
|
+
from edsl.utilities.decorators import jupyter_nb_handler
|
27
15
|
|
28
16
|
|
29
17
|
class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
@@ -42,13 +30,13 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
42
30
|
|
43
31
|
async def run_async_generator(
|
44
32
|
self,
|
45
|
-
cache: Cache,
|
33
|
+
cache: "Cache",
|
46
34
|
n: int = 1,
|
47
35
|
debug: bool = False,
|
48
36
|
stop_on_exception: bool = False,
|
49
37
|
sidecar_model: "LanguageModel" = None,
|
50
38
|
total_interviews: Optional[List["Interview"]] = None,
|
51
|
-
) -> AsyncGenerator[Result, None]:
|
39
|
+
) -> AsyncGenerator["Result", None]:
|
52
40
|
"""Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
53
41
|
|
54
42
|
Completed tasks are yielded as they are completed.
|
@@ -100,6 +88,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
100
88
|
self.total_interviews.append(interview)
|
101
89
|
|
102
90
|
async def run_async(self, cache=None) -> Results:
|
91
|
+
from edsl.results.Results import Results
|
92
|
+
|
103
93
|
if cache is None:
|
104
94
|
self.cache = Cache()
|
105
95
|
else:
|
@@ -110,6 +100,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
110
100
|
return Results(survey=self.jobs.survey, data=data)
|
111
101
|
|
112
102
|
def simple_run(self):
|
103
|
+
from edsl.results.Results import Results
|
104
|
+
|
113
105
|
data = asyncio.run(self.run_async())
|
114
106
|
return Results(survey=self.jobs.survey, data=data)
|
115
107
|
|
@@ -169,6 +161,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
169
161
|
question_name + "_raw_model_response"
|
170
162
|
] = result["raw_model_response"]
|
171
163
|
|
164
|
+
from edsl.results.Result import Result
|
165
|
+
|
172
166
|
result = Result(
|
173
167
|
agent=interview.agent,
|
174
168
|
scenario=interview.scenario,
|
@@ -197,6 +191,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
197
191
|
print_exceptions: bool = True,
|
198
192
|
) -> "Coroutine":
|
199
193
|
"""Runs a collection of interviews, handling both async and sync contexts."""
|
194
|
+
from rich.console import Console
|
195
|
+
|
200
196
|
console = Console()
|
201
197
|
self.results = []
|
202
198
|
self.start_time = time.monotonic()
|
@@ -204,6 +200,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
204
200
|
self.cache = cache
|
205
201
|
self.sidecar_model = sidecar_model
|
206
202
|
|
203
|
+
from edsl.results.Results import Results
|
204
|
+
|
207
205
|
if not progress_bar:
|
208
206
|
# print("Running without progress bar")
|
209
207
|
with cache as c:
|
@@ -225,6 +223,8 @@ class JobsRunnerAsyncio(JobsRunnerStatusMixin):
|
|
225
223
|
results = Results(survey=self.jobs.survey, data=self.results)
|
226
224
|
else:
|
227
225
|
# print("Running with progress bar")
|
226
|
+
from rich.live import Live
|
227
|
+
from rich.console import Console
|
228
228
|
|
229
229
|
def generate_table():
|
230
230
|
return self.status_table(self.results, self.elapsed_time)
|
@@ -144,12 +144,16 @@ class QuestionTaskCreator(UserList):
|
|
144
144
|
self.task_status = TaskStatus.FAILED
|
145
145
|
raise e
|
146
146
|
|
147
|
-
if "
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
147
|
+
if results.get("cache_used", False):
|
148
|
+
self.tokens_bucket.add_tokens(requested_tokens)
|
149
|
+
self.requests_bucket.add_tokens(1)
|
150
|
+
self.from_cache = True
|
151
|
+
# Turbo mode means that we don't wait for tokens or requests.
|
152
|
+
self.tokens_bucket.turbo_mode_on()
|
153
|
+
self.requests_bucket.turbo_mode_on()
|
154
|
+
else:
|
155
|
+
self.tokens_bucket.turbo_mode_off()
|
156
|
+
self.requests_bucket.turbo_mode_off()
|
153
157
|
|
154
158
|
_ = results.pop("cached_response", None)
|
155
159
|
|
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
|
|
@@ -7,26 +7,18 @@ import asyncio
|
|
7
7
|
import json
|
8
8
|
import time
|
9
9
|
import os
|
10
|
-
|
11
10
|
from typing import Coroutine, Any, Callable, Type, List, get_type_hints
|
12
|
-
|
13
|
-
from abc import ABC, abstractmethod, ABCMeta
|
14
|
-
|
15
|
-
from rich.table import Table
|
11
|
+
from abc import ABC, abstractmethod
|
16
12
|
|
17
13
|
from edsl.config import CONFIG
|
18
14
|
|
19
|
-
from edsl.utilities.utilities import clean_json
|
20
15
|
from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
21
16
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
17
|
+
|
22
18
|
from edsl.language_models.repair import repair
|
23
|
-
from edsl.exceptions.language_models import LanguageModelAttributeTypeError
|
24
19
|
from edsl.enums import InferenceServiceType
|
25
20
|
from edsl.Base import RichPrintingMixin, PersistenceMixin
|
26
|
-
from edsl.data.Cache import Cache
|
27
21
|
from edsl.enums import service_to_api_keyname
|
28
|
-
|
29
|
-
|
30
22
|
from edsl.exceptions import MissingAPIKeyError
|
31
23
|
from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
|
32
24
|
|
@@ -291,7 +283,7 @@ class LanguageModel(
|
|
291
283
|
self,
|
292
284
|
user_prompt: str,
|
293
285
|
system_prompt: str,
|
294
|
-
cache,
|
286
|
+
cache: "Cache",
|
295
287
|
iteration: int = 0,
|
296
288
|
encoded_image=None,
|
297
289
|
) -> tuple[dict, bool, str]:
|
@@ -331,12 +323,10 @@ class LanguageModel(
|
|
331
323
|
image_hash = hashlib.md5(encoded_image.encode()).hexdigest()
|
332
324
|
cache_call_params["user_prompt"] = f"{user_prompt} {image_hash}"
|
333
325
|
|
334
|
-
cached_response = cache.fetch(**cache_call_params)
|
335
|
-
|
326
|
+
cached_response, cache_key = cache.fetch(**cache_call_params)
|
336
327
|
if cached_response:
|
337
328
|
response = json.loads(cached_response)
|
338
329
|
cache_used = True
|
339
|
-
cache_key = None
|
340
330
|
else:
|
341
331
|
remote_call = hasattr(self, "remote") and self.remote
|
342
332
|
f = (
|
@@ -348,7 +338,7 @@ class LanguageModel(
|
|
348
338
|
if encoded_image:
|
349
339
|
params["encoded_image"] = encoded_image
|
350
340
|
response = await f(**params)
|
351
|
-
|
341
|
+
new_cache_key = cache.store(
|
352
342
|
user_prompt=user_prompt,
|
353
343
|
model=str(self.model),
|
354
344
|
parameters=self.parameters,
|
@@ -356,6 +346,7 @@ class LanguageModel(
|
|
356
346
|
response=response,
|
357
347
|
iteration=iteration,
|
358
348
|
)
|
349
|
+
assert new_cache_key == cache_key
|
359
350
|
cache_used = False
|
360
351
|
|
361
352
|
return response, cache_used, cache_key
|
@@ -420,7 +411,7 @@ class LanguageModel(
|
|
420
411
|
|
421
412
|
dict_response.update(
|
422
413
|
{
|
423
|
-
"
|
414
|
+
"cache_used": cache_used,
|
424
415
|
"cache_key": cache_key,
|
425
416
|
"usage": raw_response.get("usage", {}),
|
426
417
|
"raw_model_response": raw_response,
|
@@ -490,6 +481,8 @@ class LanguageModel(
|
|
490
481
|
|
491
482
|
def rich_print(self):
|
492
483
|
"""Display an object as a table."""
|
484
|
+
from rich.table import Table
|
485
|
+
|
493
486
|
table = Table(title="Language Model")
|
494
487
|
table.add_column("Attribute", style="bold")
|
495
488
|
table.add_column("Value")
|
@@ -501,7 +494,12 @@ class LanguageModel(
|
|
501
494
|
return table
|
502
495
|
|
503
496
|
@classmethod
|
504
|
-
def example(
|
497
|
+
def example(
|
498
|
+
cls,
|
499
|
+
test_model: bool = False,
|
500
|
+
canned_response: str = "Hello world",
|
501
|
+
throw_exception: bool = False,
|
502
|
+
):
|
505
503
|
"""Return a default instance of the class.
|
506
504
|
|
507
505
|
>>> from edsl.language_models import LanguageModel
|
@@ -526,6 +524,8 @@ class LanguageModel(
|
|
526
524
|
) -> dict[str, Any]:
|
527
525
|
await asyncio.sleep(0.1)
|
528
526
|
# return {"message": """{"answer": "Hello, world"}"""}
|
527
|
+
if throw_exception:
|
528
|
+
raise Exception("This is a test error")
|
529
529
|
return {"message": f'{{"answer": "{canned_response}"}}'}
|
530
530
|
|
531
531
|
def parse_response(self, raw_response: dict[str, Any]) -> str:
|
@@ -5,7 +5,7 @@ from edsl import Model
|
|
5
5
|
from edsl.language_models import LanguageModel
|
6
6
|
from edsl.Base import Base
|
7
7
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
8
|
-
from edsl.utilities import is_valid_variable_name
|
8
|
+
from edsl.utilities.utilities import is_valid_variable_name
|
9
9
|
from edsl.utilities.utilities import dict_hash
|
10
10
|
|
11
11
|
|
edsl/language_models/repair.py
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
import json
|
2
2
|
import asyncio
|
3
3
|
import warnings
|
4
|
-
from rich import print
|
5
|
-
from rich.console import Console
|
6
|
-
from rich.syntax import Syntax
|
7
|
-
|
8
|
-
from edsl.utilities.utilities import clean_json
|
9
|
-
|
10
|
-
from edsl.utilities.repair_functions import extract_json_from_string
|
11
4
|
|
12
5
|
|
13
6
|
async def async_repair(
|
14
7
|
bad_json, error_message="", user_prompt=None, system_prompt=None, cache=None
|
15
8
|
):
|
9
|
+
from edsl.utilities.utilities import clean_json
|
10
|
+
|
16
11
|
s = clean_json(bad_json)
|
17
12
|
|
18
13
|
try:
|
@@ -27,6 +22,8 @@ async def async_repair(
|
|
27
22
|
return valid_dict, success
|
28
23
|
|
29
24
|
try:
|
25
|
+
from edsl.utilities.repair_functions import extract_json_from_string
|
26
|
+
|
30
27
|
valid_dict = extract_json_from_string(s)
|
31
28
|
success = True
|
32
29
|
except ValueError:
|
@@ -98,6 +95,10 @@ async def async_repair(
|
|
98
95
|
except json.JSONDecodeError:
|
99
96
|
valid_dict = {}
|
100
97
|
success = False
|
98
|
+
from rich import print
|
99
|
+
from rich.console import Console
|
100
|
+
from rich.syntax import Syntax
|
101
|
+
|
101
102
|
console = Console()
|
102
103
|
error_message = (
|
103
104
|
f"All repairs. failed. LLM Model given [red]{str(bad_json)}[/red]"
|
edsl/notebooks/Notebook.py
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
"""A Notebook is a utility class that allows you to easily share/pull ipynbs from Coop."""
|
2
2
|
|
3
|
+
from __future__ import annotations
|
3
4
|
import json
|
4
|
-
import nbformat
|
5
|
-
from nbconvert import HTMLExporter
|
6
5
|
from typing import Dict, List, Optional
|
7
|
-
from
|
6
|
+
from uuid import uuid4
|
8
7
|
from edsl.Base import Base
|
9
|
-
from edsl.utilities.decorators import
|
10
|
-
add_edsl_version,
|
11
|
-
remove_edsl_version,
|
12
|
-
)
|
8
|
+
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
13
9
|
|
14
10
|
|
15
11
|
class Notebook(Base):
|
@@ -34,6 +30,8 @@ class Notebook(Base):
|
|
34
30
|
If no path is provided, assume this code is run in a notebook and try to load the current notebook from file.
|
35
31
|
:param name: A name for the Notebook.
|
36
32
|
"""
|
33
|
+
import nbformat
|
34
|
+
|
37
35
|
# Load current notebook path as fallback (VS Code only)
|
38
36
|
path = path or globals().get("__vsc_ipynb_file__")
|
39
37
|
if data is not None:
|
@@ -134,6 +132,9 @@ class Notebook(Base):
|
|
134
132
|
"""
|
135
133
|
Return HTML representation of Notebook.
|
136
134
|
"""
|
135
|
+
from nbconvert import HTMLExporter
|
136
|
+
import nbformat
|
137
|
+
|
137
138
|
notebook = nbformat.from_dict(self.data)
|
138
139
|
html_exporter = HTMLExporter(template_name="basic")
|
139
140
|
(body, _) = html_exporter.from_notebook_node(notebook)
|
@@ -174,6 +175,8 @@ class Notebook(Base):
|
|
174
175
|
"""
|
175
176
|
Display a Notebook as a rich table.
|
176
177
|
"""
|
178
|
+
from rich.table import Table
|
179
|
+
|
177
180
|
table_data, column_names = self._table()
|
178
181
|
table = Table(title=f"{self.__class__.__name__} Attributes")
|
179
182
|
for column in column_names:
|
@@ -186,10 +189,13 @@ class Notebook(Base):
|
|
186
189
|
return table
|
187
190
|
|
188
191
|
@classmethod
|
189
|
-
def example(cls) ->
|
192
|
+
def example(cls, randomize: bool = False) -> Notebook:
|
190
193
|
"""
|
191
|
-
|
194
|
+
Returns an example Notebook instance.
|
195
|
+
|
196
|
+
:param randomize: If True, adds a random string one of the cells' output.
|
192
197
|
"""
|
198
|
+
addition = "" if not randomize else str(uuid4())
|
193
199
|
cells = [
|
194
200
|
{
|
195
201
|
"cell_type": "markdown",
|
@@ -204,7 +210,7 @@ class Notebook(Base):
|
|
204
210
|
{
|
205
211
|
"name": "stdout",
|
206
212
|
"output_type": "stream",
|
207
|
-
"text": "Hello world!\n",
|
213
|
+
"text": f"Hello world!\n{addition}",
|
208
214
|
}
|
209
215
|
],
|
210
216
|
"source": 'print("Hello world!")',
|
edsl/questions/QuestionBase.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
"""This module contains the Question class, which is the base class for all questions in EDSL."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
|
+
import time
|
4
5
|
from abc import ABC, abstractmethod
|
5
|
-
from rich.table import Table
|
6
6
|
from typing import Any, Type, Optional, List, Callable
|
7
7
|
import copy
|
8
8
|
|
@@ -12,7 +12,7 @@ from edsl.exceptions import (
|
|
12
12
|
)
|
13
13
|
from edsl.questions.descriptors import QuestionNameDescriptor, QuestionTextDescriptor
|
14
14
|
|
15
|
-
|
15
|
+
|
16
16
|
from edsl.questions.AnswerValidatorMixin import AnswerValidatorMixin
|
17
17
|
from edsl.questions.RegisterQuestionsMeta import RegisterQuestionsMeta
|
18
18
|
from edsl.Base import PersistenceMixin, RichPrintingMixin
|
@@ -124,6 +124,8 @@ class QuestionBase(
|
|
124
124
|
:param model: The language model to use. If None, assumes does not matter.
|
125
125
|
|
126
126
|
"""
|
127
|
+
from edsl.prompts.registry import get_classes as prompt_lookup
|
128
|
+
|
127
129
|
applicable_prompts = prompt_lookup(
|
128
130
|
component_type="question_instructions",
|
129
131
|
question_type=cls.question_type,
|
@@ -496,6 +498,8 @@ class QuestionBase(
|
|
496
498
|
|
497
499
|
def rich_print(self):
|
498
500
|
"""Print the question in a rich format."""
|
501
|
+
from rich.table import Table
|
502
|
+
|
499
503
|
table = Table(show_header=True, header_style="bold magenta")
|
500
504
|
table.add_column("Question Name", style="dim")
|
501
505
|
table.add_column("Question Type")
|
edsl/questions/QuestionBudget.py
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import random
|
3
|
-
import textwrap
|
4
3
|
from typing import Any, Optional, Union
|
5
4
|
from edsl.questions.QuestionBase import QuestionBase
|
6
5
|
from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
|
7
|
-
from edsl.scenarios import Scenario
|
8
|
-
from edsl.utilities import random_string
|
9
6
|
|
10
7
|
|
11
8
|
class QuestionBudget(QuestionBase):
|
@@ -46,7 +43,7 @@ class QuestionBudget(QuestionBase):
|
|
46
43
|
return answer
|
47
44
|
|
48
45
|
def _translate_answer_code_to_answer(
|
49
|
-
self, answer_codes: dict[str, int], scenario: Scenario = None
|
46
|
+
self, answer_codes: dict[str, int], scenario: "Scenario" = None
|
50
47
|
):
|
51
48
|
"""
|
52
49
|
Translate the answer codes to the actual answers.
|
@@ -63,6 +60,8 @@ class QuestionBudget(QuestionBase):
|
|
63
60
|
|
64
61
|
def _simulate_answer(self, human_readable=True):
|
65
62
|
"""Simulate a valid answer for debugging purposes (what the validator expects)."""
|
63
|
+
from edsl.utilities.utilities import random_string
|
64
|
+
|
66
65
|
if human_readable:
|
67
66
|
keys = self.question_options
|
68
67
|
else:
|
@@ -163,8 +162,8 @@ def main():
|
|
163
162
|
|
164
163
|
|
165
164
|
if __name__ == "__main__":
|
166
|
-
q = QuestionBudget.example()
|
167
|
-
results = q.run()
|
165
|
+
# q = QuestionBudget.example()
|
166
|
+
# results = q.run()
|
168
167
|
|
169
168
|
import doctest
|
170
169
|
|
@@ -9,8 +9,6 @@ from edsl.questions.descriptors import (
|
|
9
9
|
IntegerDescriptor,
|
10
10
|
QuestionOptionsDescriptor,
|
11
11
|
)
|
12
|
-
from edsl.scenarios import Scenario
|
13
|
-
from edsl.utilities import random_string
|
14
12
|
|
15
13
|
|
16
14
|
class QuestionCheckBox(QuestionBase):
|
@@ -55,13 +53,17 @@ class QuestionCheckBox(QuestionBase):
|
|
55
53
|
self._validate_answer_checkbox(answer)
|
56
54
|
return answer
|
57
55
|
|
58
|
-
def _translate_answer_code_to_answer(
|
56
|
+
def _translate_answer_code_to_answer(
|
57
|
+
self, answer_codes, scenario: "Scenario" = None
|
58
|
+
):
|
59
59
|
"""
|
60
60
|
Translate the answer code to the actual answer.
|
61
61
|
|
62
62
|
For example, for question options ["a", "b", "c"],the answer codes are 0, 1, and 2.
|
63
63
|
The LLM will respond with [0,1] and this code will translate it to ["a","b"].
|
64
64
|
"""
|
65
|
+
from edsl.scenarios.Scenario import Scenario
|
66
|
+
|
65
67
|
scenario = scenario or Scenario()
|
66
68
|
translated_options = [
|
67
69
|
Template(option).render(scenario) for option in self.question_options
|
@@ -73,6 +75,8 @@ class QuestionCheckBox(QuestionBase):
|
|
73
75
|
|
74
76
|
def _simulate_answer(self, human_readable=True) -> dict[str, Union[int, str]]:
|
75
77
|
"""Simulate a valid answer for debugging purposes."""
|
78
|
+
from edsl.utilities.utilities import random_string
|
79
|
+
|
76
80
|
min_selections = self.min_selections or 1
|
77
81
|
max_selections = self.max_selections or len(self.question_options)
|
78
82
|
num_selections = random.randint(min_selections, max_selections)
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
2
2
|
from typing import Any
|
3
3
|
from edsl.questions.QuestionBase import QuestionBase
|
4
4
|
from edsl.questions.descriptors import AnswerTemplateDescriptor
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
5
|
|
8
6
|
|
9
7
|
class QuestionExtract(QuestionBase):
|
@@ -44,12 +42,14 @@ class QuestionExtract(QuestionBase):
|
|
44
42
|
self._validate_answer_extract(answer)
|
45
43
|
return answer
|
46
44
|
|
47
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
45
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
48
46
|
"""Return the answer in a human-readable format."""
|
49
47
|
return answer
|
50
48
|
|
51
49
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
52
50
|
"""Simulate a valid answer for debugging purposes."""
|
51
|
+
from edsl.utilities.utilities import random_string
|
52
|
+
|
53
53
|
return {
|
54
54
|
"answer": {key: random_string() for key in self.answer_template.keys()},
|
55
55
|
"comment": random_string(),
|
@@ -106,6 +106,8 @@ def main():
|
|
106
106
|
q.to_dict()
|
107
107
|
assert q.from_dict(q.to_dict()) == q
|
108
108
|
|
109
|
+
|
110
|
+
if __name__ == "__main__":
|
109
111
|
import doctest
|
110
112
|
|
111
113
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -1,9 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import textwrap
|
3
3
|
from typing import Any, Optional
|
4
|
+
from uuid import uuid4
|
4
5
|
from edsl.questions.QuestionBase import QuestionBase
|
5
|
-
from edsl.scenarios import Scenario
|
6
|
-
from edsl.utilities import random_string
|
7
6
|
|
8
7
|
|
9
8
|
class QuestionFreeText(QuestionBase):
|
@@ -43,12 +42,14 @@ class QuestionFreeText(QuestionBase):
|
|
43
42
|
self._validate_answer_key_value(answer, "answer", str)
|
44
43
|
return answer
|
45
44
|
|
46
|
-
def _translate_answer_code_to_answer(self, answer, scenario: Scenario = None):
|
45
|
+
def _translate_answer_code_to_answer(self, answer, scenario: "Scenario" = None):
|
47
46
|
"""Do nothing, because the answer is already in a human-readable format."""
|
48
47
|
return answer
|
49
48
|
|
50
49
|
def _simulate_answer(self, human_readable: bool = True) -> dict[str, str]:
|
51
50
|
"""Simulate a valid answer for debugging purposes."""
|
51
|
+
from edsl.utilities.utilities import random_string
|
52
|
+
|
52
53
|
return {"answer": random_string()}
|
53
54
|
|
54
55
|
@property
|
@@ -65,9 +66,10 @@ class QuestionFreeText(QuestionBase):
|
|
65
66
|
return question_html_content
|
66
67
|
|
67
68
|
@classmethod
|
68
|
-
def example(cls) -> QuestionFreeText:
|
69
|
+
def example(cls, randomize: bool = False) -> QuestionFreeText:
|
69
70
|
"""Return an example instance of a free text question."""
|
70
|
-
|
71
|
+
addition = "" if not randomize else str(uuid4())
|
72
|
+
return cls(question_name="how_are_you", question_text=f"How are you?{addition}")
|
71
73
|
|
72
74
|
|
73
75
|
def main():
|