edsl 0.1.27.dev2__py3-none-any.whl → 0.1.28__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 +99 -22
- edsl/BaseDiff.py +260 -0
- edsl/__init__.py +4 -0
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +26 -5
- edsl/agents/AgentList.py +62 -7
- edsl/agents/Invigilator.py +4 -9
- edsl/agents/InvigilatorBase.py +5 -5
- edsl/agents/descriptors.py +3 -1
- edsl/conjure/AgentConstructionMixin.py +152 -0
- edsl/conjure/Conjure.py +56 -0
- edsl/conjure/InputData.py +628 -0
- edsl/conjure/InputDataCSV.py +48 -0
- edsl/conjure/InputDataMixinQuestionStats.py +182 -0
- edsl/conjure/InputDataPyRead.py +91 -0
- edsl/conjure/InputDataSPSS.py +8 -0
- edsl/conjure/InputDataStata.py +8 -0
- edsl/conjure/QuestionOptionMixin.py +76 -0
- edsl/conjure/QuestionTypeMixin.py +23 -0
- edsl/conjure/RawQuestion.py +65 -0
- edsl/conjure/SurveyResponses.py +7 -0
- edsl/conjure/__init__.py +9 -4
- edsl/conjure/examples/placeholder.txt +0 -0
- edsl/conjure/naming_utilities.py +263 -0
- edsl/conjure/utilities.py +165 -28
- edsl/conversation/Conversation.py +238 -0
- edsl/conversation/car_buying.py +58 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/coop.py +191 -12
- edsl/coop/utils.py +20 -2
- edsl/data/Cache.py +55 -17
- edsl/data/CacheHandler.py +10 -9
- edsl/inference_services/AnthropicService.py +1 -0
- edsl/inference_services/DeepInfraService.py +20 -13
- edsl/inference_services/GoogleService.py +7 -1
- edsl/inference_services/InferenceServicesCollection.py +33 -7
- edsl/inference_services/OpenAIService.py +17 -10
- edsl/inference_services/models_available_cache.py +69 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/Jobs.py +240 -36
- edsl/jobs/buckets/BucketCollection.py +9 -3
- edsl/jobs/interviews/Interview.py +4 -1
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +24 -10
- edsl/jobs/interviews/retry_management.py +4 -4
- edsl/jobs/runners/JobsRunnerAsyncio.py +87 -45
- edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
- edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
- edsl/language_models/LanguageModel.py +37 -44
- edsl/language_models/ModelList.py +96 -0
- edsl/language_models/registry.py +14 -0
- edsl/language_models/repair.py +95 -24
- edsl/notebooks/Notebook.py +119 -31
- edsl/questions/QuestionBase.py +109 -12
- edsl/questions/descriptors.py +5 -2
- edsl/questions/question_registry.py +7 -0
- edsl/results/Result.py +20 -8
- edsl/results/Results.py +85 -11
- edsl/results/ResultsDBMixin.py +3 -6
- edsl/results/ResultsExportMixin.py +47 -16
- edsl/results/ResultsToolsMixin.py +5 -5
- edsl/scenarios/Scenario.py +59 -5
- edsl/scenarios/ScenarioList.py +97 -40
- edsl/study/ObjectEntry.py +97 -0
- edsl/study/ProofOfWork.py +110 -0
- edsl/study/SnapShot.py +77 -0
- edsl/study/Study.py +491 -0
- edsl/study/__init__.py +2 -0
- edsl/surveys/Survey.py +79 -31
- edsl/surveys/SurveyExportMixin.py +21 -3
- edsl/utilities/__init__.py +1 -0
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/interface.py +24 -28
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/utilities.py +57 -2
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/METADATA +43 -17
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/RECORD +83 -55
- edsl-0.1.28.dist-info/entry_points.txt +3 -0
- edsl/conjure/RawResponseColumn.py +0 -327
- edsl/conjure/SurveyBuilder.py +0 -308
- edsl/conjure/SurveyBuilderCSV.py +0 -78
- edsl/conjure/SurveyBuilderSPSS.py +0 -118
- edsl/data/RemoteDict.py +0 -103
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/LICENSE +0 -0
- {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/WHEEL +0 -0
edsl/jobs/Jobs.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# """The Jobs class is a collection of agents, scenarios and models and one survey."""
|
2
2
|
from __future__ import annotations
|
3
3
|
import os
|
4
|
+
import warnings
|
4
5
|
from itertools import product
|
5
6
|
from typing import Optional, Union, Sequence, Generator
|
6
7
|
from edsl import Model
|
7
8
|
from edsl.agents import Agent
|
9
|
+
from edsl.agents.AgentList import AgentList
|
8
10
|
from edsl.Base import Base
|
9
11
|
from edsl.data.Cache import Cache
|
10
12
|
from edsl.data.CacheHandler import CacheHandler
|
@@ -17,7 +19,11 @@ from edsl.jobs.interviews.Interview import Interview
|
|
17
19
|
from edsl.language_models import LanguageModel
|
18
20
|
from edsl.results import Results
|
19
21
|
from edsl.scenarios import Scenario
|
22
|
+
from edsl import ScenarioList
|
20
23
|
from edsl.surveys import Survey
|
24
|
+
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
25
|
+
|
26
|
+
from edsl.language_models.ModelList import ModelList
|
21
27
|
|
22
28
|
from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
|
23
29
|
|
@@ -44,11 +50,54 @@ class Jobs(Base):
|
|
44
50
|
:param scenarios: a list of scenarios
|
45
51
|
"""
|
46
52
|
self.survey = survey
|
47
|
-
self.agents = agents
|
48
|
-
self.
|
49
|
-
self.
|
53
|
+
self.agents: AgentList = agents
|
54
|
+
self.scenarios: ScenarioList = scenarios
|
55
|
+
self.models = models
|
56
|
+
|
50
57
|
self.__bucket_collection = None
|
51
58
|
|
59
|
+
@property
|
60
|
+
def models(self):
|
61
|
+
return self._models
|
62
|
+
|
63
|
+
@models.setter
|
64
|
+
def models(self, value):
|
65
|
+
if value:
|
66
|
+
if not isinstance(value, ModelList):
|
67
|
+
self._models = ModelList(value)
|
68
|
+
else:
|
69
|
+
self._models = value
|
70
|
+
else:
|
71
|
+
self._models = ModelList([])
|
72
|
+
|
73
|
+
@property
|
74
|
+
def agents(self):
|
75
|
+
return self._agents
|
76
|
+
|
77
|
+
@agents.setter
|
78
|
+
def agents(self, value):
|
79
|
+
if value:
|
80
|
+
if not isinstance(value, AgentList):
|
81
|
+
self._agents = AgentList(value)
|
82
|
+
else:
|
83
|
+
self._agents = value
|
84
|
+
else:
|
85
|
+
self._agents = AgentList([])
|
86
|
+
|
87
|
+
@property
|
88
|
+
def scenarios(self):
|
89
|
+
return self._scenarios
|
90
|
+
|
91
|
+
@scenarios.setter
|
92
|
+
def scenarios(self, value):
|
93
|
+
if value:
|
94
|
+
if not isinstance(value, ScenarioList):
|
95
|
+
self._scenarios = ScenarioList(value)
|
96
|
+
else:
|
97
|
+
self._scenarios = value
|
98
|
+
else:
|
99
|
+
self._scenarios = ScenarioList([])
|
100
|
+
|
52
101
|
def by(
|
53
102
|
self,
|
54
103
|
*args: Union[
|
@@ -68,10 +117,10 @@ class Jobs(Base):
|
|
68
117
|
>>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
|
69
118
|
>>> j = Jobs(survey = Survey(questions=[q]))
|
70
119
|
>>> j
|
71
|
-
Jobs(survey=Survey(...), agents=[], models=[], scenarios=[])
|
120
|
+
Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
|
72
121
|
>>> from edsl import Agent; a = Agent(traits = {"status": "Sad"})
|
73
122
|
>>> j.by(a).agents
|
74
|
-
[Agent(traits = {'status': 'Sad'})]
|
123
|
+
AgentList([Agent(traits = {'status': 'Sad'})])
|
75
124
|
|
76
125
|
:param args: objects or a sequence (list, tuple, ...) of objects of the same type
|
77
126
|
|
@@ -101,7 +150,7 @@ class Jobs(Base):
|
|
101
150
|
|
102
151
|
>>> from edsl.jobs import Jobs
|
103
152
|
>>> Jobs.example().prompts()
|
104
|
-
Dataset(
|
153
|
+
Dataset(...)
|
105
154
|
"""
|
106
155
|
|
107
156
|
interviews = self.interviews()
|
@@ -131,6 +180,17 @@ class Jobs(Base):
|
|
131
180
|
]
|
132
181
|
)
|
133
182
|
|
183
|
+
@staticmethod
|
184
|
+
def _get_container_class(object):
|
185
|
+
from edsl import AgentList
|
186
|
+
|
187
|
+
if isinstance(object, Agent):
|
188
|
+
return AgentList
|
189
|
+
elif isinstance(object, Scenario):
|
190
|
+
return ScenarioList
|
191
|
+
else:
|
192
|
+
return list
|
193
|
+
|
134
194
|
@staticmethod
|
135
195
|
def _turn_args_to_list(args):
|
136
196
|
"""Return a list of the first argument if it is a sequence, otherwise returns a list of all the arguments."""
|
@@ -149,9 +209,11 @@ class Jobs(Base):
|
|
149
209
|
return len(args) == 1 and isinstance(args[0], Sequence)
|
150
210
|
|
151
211
|
if did_user_pass_a_sequence(args):
|
152
|
-
|
212
|
+
container_class = Jobs._get_container_class(args[0][0])
|
213
|
+
return container_class(args[0])
|
153
214
|
else:
|
154
|
-
|
215
|
+
container_class = Jobs._get_container_class(args[0])
|
216
|
+
return container_class(args)
|
155
217
|
|
156
218
|
def _get_current_objects_of_this_type(
|
157
219
|
self, object: Union[Agent, Scenario, LanguageModel]
|
@@ -161,7 +223,7 @@ class Jobs(Base):
|
|
161
223
|
>>> from edsl.jobs import Jobs
|
162
224
|
>>> j = Jobs.example()
|
163
225
|
>>> j._get_current_objects_of_this_type(j.agents[0])
|
164
|
-
([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'})], 'agents')
|
226
|
+
(AgentList([Agent(traits = {'status': 'Joyful'}), Agent(traits = {'status': 'Sad'})]), 'agents')
|
165
227
|
"""
|
166
228
|
class_to_key = {
|
167
229
|
Agent: "agents",
|
@@ -181,6 +243,17 @@ class Jobs(Base):
|
|
181
243
|
current_objects = getattr(self, key, None)
|
182
244
|
return current_objects, key
|
183
245
|
|
246
|
+
@staticmethod
|
247
|
+
def _get_empty_container_object(object):
|
248
|
+
from edsl import AgentList
|
249
|
+
|
250
|
+
if isinstance(object, Agent):
|
251
|
+
return AgentList([])
|
252
|
+
elif isinstance(object, Scenario):
|
253
|
+
return ScenarioList([])
|
254
|
+
else:
|
255
|
+
return []
|
256
|
+
|
184
257
|
@staticmethod
|
185
258
|
def _merge_objects(passed_objects, current_objects) -> list:
|
186
259
|
"""
|
@@ -192,7 +265,7 @@ class Jobs(Base):
|
|
192
265
|
>>> Jobs(survey = [])._merge_objects([1,2,3], [4,5,6])
|
193
266
|
[5, 6, 7, 6, 7, 8, 7, 8, 9]
|
194
267
|
"""
|
195
|
-
new_objects = []
|
268
|
+
new_objects = Jobs._get_empty_container_object(passed_objects[0])
|
196
269
|
for current_object in current_objects:
|
197
270
|
for new_object in passed_objects:
|
198
271
|
new_objects.append(current_object + new_object)
|
@@ -284,6 +357,54 @@ class Jobs(Base):
|
|
284
357
|
)
|
285
358
|
return links
|
286
359
|
|
360
|
+
def __hash__(self):
|
361
|
+
"""Allow the model to be used as a key in a dictionary."""
|
362
|
+
from edsl.utilities.utilities import dict_hash
|
363
|
+
|
364
|
+
return dict_hash(self.to_dict())
|
365
|
+
|
366
|
+
def _output(self, message) -> None:
|
367
|
+
"""Check if a Job is verbose. If so, print the message."""
|
368
|
+
if self.verbose:
|
369
|
+
print(message)
|
370
|
+
|
371
|
+
def _check_parameters(self, strict=False) -> None:
|
372
|
+
"""Check if the parameters in the survey and scenarios are consistent.
|
373
|
+
|
374
|
+
>>> from edsl import QuestionFreeText
|
375
|
+
>>> q = QuestionFreeText(question_text = "{{poo}}", question_name = "ugly_question")
|
376
|
+
>>> j = Jobs(survey = Survey(questions=[q]))
|
377
|
+
>>> with warnings.catch_warnings(record=True) as w:
|
378
|
+
... j._check_parameters()
|
379
|
+
... assert len(w) == 1
|
380
|
+
... assert issubclass(w[-1].category, UserWarning)
|
381
|
+
... assert "The following parameters are in the survey but not in the scenarios" in str(w[-1].message)
|
382
|
+
|
383
|
+
>>> q = QuestionFreeText(question_text = "{{poo}}", question_name = "ugly_question")
|
384
|
+
>>> s = Scenario({'plop': "A", 'poo': "B"})
|
385
|
+
>>> j = Jobs(survey = Survey(questions=[q])).by(s)
|
386
|
+
>>> j._check_parameters(strict = True)
|
387
|
+
Traceback (most recent call last):
|
388
|
+
...
|
389
|
+
ValueError: The following parameters are in the scenarios but not in the survey: {'plop'}
|
390
|
+
"""
|
391
|
+
survey_parameters: set = self.survey.parameters
|
392
|
+
scenario_parameters: set = self.scenarios.parameters
|
393
|
+
|
394
|
+
msg1, msg2 = None, None
|
395
|
+
|
396
|
+
if in_survey_but_not_in_scenarios := survey_parameters - scenario_parameters:
|
397
|
+
msg1 = f"The following parameters are in the survey but not in the scenarios: {in_survey_but_not_in_scenarios}"
|
398
|
+
if in_scenarios_but_not_in_survey := scenario_parameters - survey_parameters:
|
399
|
+
msg2 = f"The following parameters are in the scenarios but not in the survey: {in_scenarios_but_not_in_survey}"
|
400
|
+
|
401
|
+
if msg1 or msg2:
|
402
|
+
message = "\n".join(filter(None, [msg1, msg2]))
|
403
|
+
if strict:
|
404
|
+
raise ValueError(message)
|
405
|
+
else:
|
406
|
+
warnings.warn(message)
|
407
|
+
|
287
408
|
def run(
|
288
409
|
self,
|
289
410
|
n: int = 1,
|
@@ -297,28 +418,41 @@ class Jobs(Base):
|
|
297
418
|
check_api_keys: bool = False,
|
298
419
|
sidecar_model: Optional[LanguageModel] = None,
|
299
420
|
batch_mode: Optional[bool] = None,
|
300
|
-
|
421
|
+
verbose: bool = False,
|
422
|
+
print_exceptions=True,
|
423
|
+
remote_cache_description: Optional[str] = None,
|
301
424
|
) -> Results:
|
302
425
|
"""
|
303
426
|
Runs the Job: conducts Interviews and returns their results.
|
304
427
|
|
305
428
|
:param n: how many times to run each interview
|
306
429
|
:param debug: prints debug messages
|
307
|
-
:param verbose: prints messages
|
308
430
|
:param progress_bar: shows a progress bar
|
309
431
|
:param stop_on_exception: stops the job if an exception is raised
|
310
432
|
:param cache: a cache object to store results
|
311
433
|
:param remote: run the job remotely
|
312
434
|
:param check_api_keys: check if the API keys are valid
|
313
|
-
:batch_mode: run the job in batch mode i.e., no expecation of interaction with the user
|
314
|
-
|
435
|
+
:param batch_mode: run the job in batch mode i.e., no expecation of interaction with the user
|
436
|
+
:param verbose: prints messages
|
437
|
+
:param remote_cache_description: specifies a description for this group of entries in the remote cache
|
315
438
|
"""
|
439
|
+
from edsl.coop.coop import Coop
|
440
|
+
|
441
|
+
self._check_parameters()
|
442
|
+
|
316
443
|
if batch_mode is not None:
|
317
444
|
raise NotImplementedError(
|
318
445
|
"Batch mode is deprecated. Please update your code to not include 'batch_mode' in the 'run' method."
|
319
446
|
)
|
320
447
|
|
321
448
|
self.remote = remote
|
449
|
+
self.verbose = verbose
|
450
|
+
|
451
|
+
try:
|
452
|
+
coop = Coop()
|
453
|
+
remote_cache = coop.edsl_settings["remote_caching"]
|
454
|
+
except Exception:
|
455
|
+
remote_cache = False
|
322
456
|
|
323
457
|
if self.remote:
|
324
458
|
## TODO: This should be a coop check
|
@@ -340,26 +474,103 @@ class Jobs(Base):
|
|
340
474
|
if cache is False:
|
341
475
|
cache = Cache()
|
342
476
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
477
|
+
if not remote_cache:
|
478
|
+
results = self._run_local(
|
479
|
+
n=n,
|
480
|
+
debug=debug,
|
481
|
+
progress_bar=progress_bar,
|
482
|
+
cache=cache,
|
483
|
+
stop_on_exception=stop_on_exception,
|
484
|
+
sidecar_model=sidecar_model,
|
485
|
+
print_exceptions=print_exceptions,
|
486
|
+
)
|
487
|
+
|
488
|
+
results.cache = cache.new_entries_cache()
|
489
|
+
|
490
|
+
self._output(f"There are {len(cache.keys()):,} entries in the local cache.")
|
491
|
+
else:
|
492
|
+
cache_difference = coop.remote_cache_get_diff(cache.keys())
|
493
|
+
|
494
|
+
client_missing_cacheentries = cache_difference.get(
|
495
|
+
"client_missing_cacheentries", []
|
496
|
+
)
|
497
|
+
|
498
|
+
missing_entry_count = len(client_missing_cacheentries)
|
499
|
+
if missing_entry_count > 0:
|
500
|
+
self._output(
|
501
|
+
f"Updating local cache with {missing_entry_count:,} new "
|
502
|
+
f"{'entry' if missing_entry_count == 1 else 'entries'} from remote..."
|
503
|
+
)
|
504
|
+
cache.add_from_dict(
|
505
|
+
{entry.key: entry for entry in client_missing_cacheentries}
|
506
|
+
)
|
507
|
+
self._output("Local cache updated!")
|
508
|
+
else:
|
509
|
+
self._output("No new entries to add to local cache.")
|
510
|
+
|
511
|
+
server_missing_cacheentry_keys = cache_difference.get(
|
512
|
+
"server_missing_cacheentry_keys", []
|
513
|
+
)
|
514
|
+
server_missing_cacheentries = [
|
515
|
+
entry
|
516
|
+
for key in server_missing_cacheentry_keys
|
517
|
+
if (entry := cache.data.get(key)) is not None
|
518
|
+
]
|
519
|
+
old_entry_keys = [key for key in cache.keys()]
|
520
|
+
|
521
|
+
self._output("Running job...")
|
522
|
+
results = self._run_local(
|
523
|
+
n=n,
|
524
|
+
debug=debug,
|
525
|
+
progress_bar=progress_bar,
|
526
|
+
cache=cache,
|
527
|
+
stop_on_exception=stop_on_exception,
|
528
|
+
sidecar_model=sidecar_model,
|
529
|
+
print_exceptions=print_exceptions,
|
530
|
+
)
|
531
|
+
self._output("Job completed!")
|
532
|
+
|
533
|
+
new_cache_entries = list(
|
534
|
+
[entry for entry in cache.values() if entry.key not in old_entry_keys]
|
535
|
+
)
|
536
|
+
server_missing_cacheentries.extend(new_cache_entries)
|
537
|
+
|
538
|
+
new_entry_count = len(server_missing_cacheentries)
|
539
|
+
if new_entry_count > 0:
|
540
|
+
self._output(
|
541
|
+
f"Updating remote cache with {new_entry_count:,} new "
|
542
|
+
f"{'entry' if new_entry_count == 1 else 'entries'}..."
|
543
|
+
)
|
544
|
+
coop.remote_cache_create_many(
|
545
|
+
server_missing_cacheentries,
|
546
|
+
visibility="private",
|
547
|
+
description=remote_cache_description,
|
548
|
+
)
|
549
|
+
self._output("Remote cache updated!")
|
550
|
+
else:
|
551
|
+
self._output("No new entries to add to remote cache.")
|
552
|
+
|
553
|
+
results.cache = cache.new_entries_cache()
|
554
|
+
|
555
|
+
self._output(f"There are {len(cache.keys()):,} entries in the local cache.")
|
353
556
|
|
354
557
|
return results
|
355
558
|
|
356
559
|
def _run_local(self, *args, **kwargs):
|
357
560
|
"""Run the job locally."""
|
358
|
-
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
359
561
|
|
360
562
|
results = JobsRunnerAsyncio(self).run(*args, **kwargs)
|
361
563
|
return results
|
362
564
|
|
565
|
+
async def run_async(self, cache=None, **kwargs):
|
566
|
+
"""Run the job asynchronously."""
|
567
|
+
results = await JobsRunnerAsyncio(self).run_async(cache=cache, **kwargs)
|
568
|
+
return results
|
569
|
+
|
570
|
+
def all_question_parameters(self):
|
571
|
+
"""Return all the fields in the questions in the survey."""
|
572
|
+
return set.union(*[question.parameters for question in self.survey.questions])
|
573
|
+
|
363
574
|
#######################
|
364
575
|
# Dunder methods
|
365
576
|
#######################
|
@@ -479,9 +690,10 @@ class Jobs(Base):
|
|
479
690
|
)
|
480
691
|
base_survey = Survey(questions=[q1, q2])
|
481
692
|
|
482
|
-
|
483
|
-
Scenario({"period": "morning"}), Scenario({"period": "afternoon"})
|
484
|
-
)
|
693
|
+
scenario_list = ScenarioList(
|
694
|
+
[Scenario({"period": "morning"}), Scenario({"period": "afternoon"})]
|
695
|
+
)
|
696
|
+
job = base_survey.by(scenario_list).by(joy_agent, sad_agent)
|
485
697
|
|
486
698
|
return job
|
487
699
|
|
@@ -516,11 +728,3 @@ if __name__ == "__main__":
|
|
516
728
|
import doctest
|
517
729
|
|
518
730
|
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
519
|
-
|
520
|
-
# from edsl.jobs import Jobs
|
521
|
-
|
522
|
-
# job = Jobs.example()
|
523
|
-
# len(job) == 8
|
524
|
-
# results, info = job.run(debug=True)
|
525
|
-
# len(results) == 8
|
526
|
-
# results
|
@@ -10,8 +10,9 @@ class BucketCollection(UserDict):
|
|
10
10
|
Models themselves are hashable, so this works.
|
11
11
|
"""
|
12
12
|
|
13
|
-
def __init__(self):
|
13
|
+
def __init__(self, infinity_buckets=False):
|
14
14
|
super().__init__()
|
15
|
+
self.infinity_buckets = infinity_buckets
|
15
16
|
|
16
17
|
def __repr__(self):
|
17
18
|
return f"BucketCollection({self.data})"
|
@@ -21,8 +22,13 @@ class BucketCollection(UserDict):
|
|
21
22
|
|
22
23
|
This will create the token and request buckets for the model."""
|
23
24
|
# compute the TPS and RPS from the model
|
24
|
-
|
25
|
-
|
25
|
+
if not self.infinity_buckets:
|
26
|
+
TPS = model.TPM / 60.0
|
27
|
+
RPS = model.RPM / 60.0
|
28
|
+
else:
|
29
|
+
TPS = float("inf")
|
30
|
+
RPS = float("inf")
|
31
|
+
|
26
32
|
# create the buckets
|
27
33
|
requests_bucket = TokenBucket(
|
28
34
|
bucket_name=model.model,
|
@@ -101,9 +101,12 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
|
|
101
101
|
self.sidecar_model = sidecar_model
|
102
102
|
|
103
103
|
# if no model bucket is passed, create an 'infinity' bucket with no rate limits
|
104
|
-
|
104
|
+
# print("model_buckets", model_buckets)
|
105
|
+
if model_buckets is None or hasattr(self.agent, "answer_question_directly"):
|
105
106
|
model_buckets = ModelBuckets.infinity_bucket()
|
106
107
|
|
108
|
+
# model_buckets = ModelBuckets.infinity_bucket()
|
109
|
+
|
107
110
|
## build the tasks using the InterviewTaskBuildingMixin
|
108
111
|
## This is the key part---it creates a task for each question,
|
109
112
|
## with dependencies on the questions that must be answered before this one can be answered.
|
@@ -158,20 +158,34 @@ class InterviewTaskBuildingMixin:
|
|
158
158
|
This in turn calls the the passed-in agent's async_answer_question method, which returns a response dictionary.
|
159
159
|
Note that is updates answers dictionary with the response.
|
160
160
|
"""
|
161
|
-
|
161
|
+
try:
|
162
|
+
invigilator = self._get_invigilator(question, debug=debug)
|
162
163
|
|
163
|
-
|
164
|
-
|
164
|
+
if self._skip_this_question(question):
|
165
|
+
return invigilator.get_failed_task_result()
|
165
166
|
|
166
|
-
|
167
|
-
|
168
|
-
|
167
|
+
response: AgentResponseDict = await self._attempt_to_answer_question(
|
168
|
+
invigilator, task
|
169
|
+
)
|
169
170
|
|
170
|
-
|
171
|
+
self._add_answer(response=response, question=question)
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
173
|
+
# With the answer to the question, we can now cancel any skipped questions
|
174
|
+
self._cancel_skipped_questions(question)
|
175
|
+
return AgentResponseDict(**response)
|
176
|
+
except Exception as e:
|
177
|
+
raise e
|
178
|
+
# import traceback
|
179
|
+
# print("Exception caught:")
|
180
|
+
# traceback.print_exc()
|
181
|
+
|
182
|
+
# # Extract and print the traceback info
|
183
|
+
# tb = e.__traceback__
|
184
|
+
# while tb is not None:
|
185
|
+
# print(f"File {tb.tb_frame.f_code.co_filename}, line {tb.tb_lineno}, in {tb.tb_frame.f_code.co_name}")
|
186
|
+
# tb = tb.tb_next
|
187
|
+
# breakpoint()
|
188
|
+
# raise e
|
175
189
|
|
176
190
|
def _add_answer(self, response: AgentResponseDict, question: QuestionBase) -> None:
|
177
191
|
"""Add the answer to the answers dictionary.
|
@@ -13,17 +13,17 @@ EDSL_MAX_BACKOFF_SEC = float(CONFIG.get("EDSL_MAX_BACKOFF_SEC"))
|
|
13
13
|
EDSL_MAX_ATTEMPTS = int(CONFIG.get("EDSL_MAX_ATTEMPTS"))
|
14
14
|
|
15
15
|
|
16
|
-
def print_retry(retry_state, print_to_terminal=
|
16
|
+
def print_retry(retry_state, print_to_terminal=True):
|
17
17
|
"Prints details on tenacity retries."
|
18
18
|
attempt_number = retry_state.attempt_number
|
19
19
|
exception = retry_state.outcome.exception()
|
20
20
|
wait_time = retry_state.next_action.sleep
|
21
|
-
# breakpoint()
|
22
21
|
if print_to_terminal:
|
23
22
|
print(
|
24
|
-
f"Attempt {attempt_number} failed with exception: {
|
23
|
+
f"Attempt {attempt_number} failed with exception:" f"{exception}",
|
25
24
|
f"now waiting {wait_time:.2f} seconds before retrying."
|
26
|
-
f"
|
25
|
+
f"Parameters: start={EDSL_BACKOFF_START_SEC}, max={EDSL_MAX_BACKOFF_SEC}, max_attempts={EDSL_MAX_ATTEMPTS}."
|
26
|
+
"\n\n",
|
27
27
|
)
|
28
28
|
|
29
29
|
|