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.
Files changed (88) hide show
  1. edsl/Base.py +99 -22
  2. edsl/BaseDiff.py +260 -0
  3. edsl/__init__.py +4 -0
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +26 -5
  6. edsl/agents/AgentList.py +62 -7
  7. edsl/agents/Invigilator.py +4 -9
  8. edsl/agents/InvigilatorBase.py +5 -5
  9. edsl/agents/descriptors.py +3 -1
  10. edsl/conjure/AgentConstructionMixin.py +152 -0
  11. edsl/conjure/Conjure.py +56 -0
  12. edsl/conjure/InputData.py +628 -0
  13. edsl/conjure/InputDataCSV.py +48 -0
  14. edsl/conjure/InputDataMixinQuestionStats.py +182 -0
  15. edsl/conjure/InputDataPyRead.py +91 -0
  16. edsl/conjure/InputDataSPSS.py +8 -0
  17. edsl/conjure/InputDataStata.py +8 -0
  18. edsl/conjure/QuestionOptionMixin.py +76 -0
  19. edsl/conjure/QuestionTypeMixin.py +23 -0
  20. edsl/conjure/RawQuestion.py +65 -0
  21. edsl/conjure/SurveyResponses.py +7 -0
  22. edsl/conjure/__init__.py +9 -4
  23. edsl/conjure/examples/placeholder.txt +0 -0
  24. edsl/conjure/naming_utilities.py +263 -0
  25. edsl/conjure/utilities.py +165 -28
  26. edsl/conversation/Conversation.py +238 -0
  27. edsl/conversation/car_buying.py +58 -0
  28. edsl/conversation/mug_negotiation.py +81 -0
  29. edsl/conversation/next_speaker_utilities.py +93 -0
  30. edsl/coop/coop.py +191 -12
  31. edsl/coop/utils.py +20 -2
  32. edsl/data/Cache.py +55 -17
  33. edsl/data/CacheHandler.py +10 -9
  34. edsl/inference_services/AnthropicService.py +1 -0
  35. edsl/inference_services/DeepInfraService.py +20 -13
  36. edsl/inference_services/GoogleService.py +7 -1
  37. edsl/inference_services/InferenceServicesCollection.py +33 -7
  38. edsl/inference_services/OpenAIService.py +17 -10
  39. edsl/inference_services/models_available_cache.py +69 -0
  40. edsl/inference_services/rate_limits_cache.py +25 -0
  41. edsl/inference_services/write_available.py +10 -0
  42. edsl/jobs/Jobs.py +240 -36
  43. edsl/jobs/buckets/BucketCollection.py +9 -3
  44. edsl/jobs/interviews/Interview.py +4 -1
  45. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +24 -10
  46. edsl/jobs/interviews/retry_management.py +4 -4
  47. edsl/jobs/runners/JobsRunnerAsyncio.py +87 -45
  48. edsl/jobs/runners/JobsRunnerStatusData.py +3 -3
  49. edsl/jobs/tasks/QuestionTaskCreator.py +4 -2
  50. edsl/language_models/LanguageModel.py +37 -44
  51. edsl/language_models/ModelList.py +96 -0
  52. edsl/language_models/registry.py +14 -0
  53. edsl/language_models/repair.py +95 -24
  54. edsl/notebooks/Notebook.py +119 -31
  55. edsl/questions/QuestionBase.py +109 -12
  56. edsl/questions/descriptors.py +5 -2
  57. edsl/questions/question_registry.py +7 -0
  58. edsl/results/Result.py +20 -8
  59. edsl/results/Results.py +85 -11
  60. edsl/results/ResultsDBMixin.py +3 -6
  61. edsl/results/ResultsExportMixin.py +47 -16
  62. edsl/results/ResultsToolsMixin.py +5 -5
  63. edsl/scenarios/Scenario.py +59 -5
  64. edsl/scenarios/ScenarioList.py +97 -40
  65. edsl/study/ObjectEntry.py +97 -0
  66. edsl/study/ProofOfWork.py +110 -0
  67. edsl/study/SnapShot.py +77 -0
  68. edsl/study/Study.py +491 -0
  69. edsl/study/__init__.py +2 -0
  70. edsl/surveys/Survey.py +79 -31
  71. edsl/surveys/SurveyExportMixin.py +21 -3
  72. edsl/utilities/__init__.py +1 -0
  73. edsl/utilities/gcp_bucket/__init__.py +0 -0
  74. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  75. edsl/utilities/gcp_bucket/simple_example.py +9 -0
  76. edsl/utilities/interface.py +24 -28
  77. edsl/utilities/repair_functions.py +28 -0
  78. edsl/utilities/utilities.py +57 -2
  79. {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/METADATA +43 -17
  80. {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/RECORD +83 -55
  81. edsl-0.1.28.dist-info/entry_points.txt +3 -0
  82. edsl/conjure/RawResponseColumn.py +0 -327
  83. edsl/conjure/SurveyBuilder.py +0 -308
  84. edsl/conjure/SurveyBuilderCSV.py +0 -78
  85. edsl/conjure/SurveyBuilderSPSS.py +0 -118
  86. edsl/data/RemoteDict.py +0 -103
  87. {edsl-0.1.27.dev2.dist-info → edsl-0.1.28.dist-info}/LICENSE +0 -0
  88. {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 or []
48
- self.models = models or []
49
- self.scenarios = scenarios or []
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([{'interview_index': [0, 0, 1, 1, 2, 2, 3, 3]}, {'question_index': ['how_feeling', 'how_feeling_yesterday', 'how_feeling', 'how_feeling_yesterday', 'how_feeling', 'how_feeling_yesterday', 'how_feeling', 'how_feeling_yesterday']}, {'user_prompt': [Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA')]}, {'scenario_index': [Scenario({'period': 'morning'}), Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'}), Scenario({'period': 'afternoon'}), Scenario({'period': 'morning'}), Scenario({'period': 'morning'}), Scenario({'period': 'afternoon'}), Scenario({'period': 'afternoon'})]}, {'system_prompt': [Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA'), Prompt(text='NA')]}])
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
- return list(args[0])
212
+ container_class = Jobs._get_container_class(args[0][0])
213
+ return container_class(args[0])
153
214
  else:
154
- return list(args)
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
- print_exceptions=False,
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
- results = self._run_local(
344
- n=n,
345
- debug=debug,
346
- progress_bar=progress_bar,
347
- cache=cache,
348
- stop_on_exception=stop_on_exception,
349
- sidecar_model=sidecar_model,
350
- print_exceptions=print_exceptions,
351
- )
352
- results.cache = cache.new_entries_cache()
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
- job = base_survey.by(
483
- Scenario({"period": "morning"}), Scenario({"period": "afternoon"})
484
- ).by(joy_agent, sad_agent)
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
- TPS = model.TPM / 60.0
25
- RPS = model.RPM / 60.0
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
- if model_buckets is None:
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
- invigilator = self._get_invigilator(question, debug=debug)
161
+ try:
162
+ invigilator = self._get_invigilator(question, debug=debug)
162
163
 
163
- if self._skip_this_question(question):
164
- return invigilator.get_failed_task_result()
164
+ if self._skip_this_question(question):
165
+ return invigilator.get_failed_task_result()
165
166
 
166
- response: AgentResponseDict = await self._attempt_to_answer_question(
167
- invigilator, task
168
- )
167
+ response: AgentResponseDict = await self._attempt_to_answer_question(
168
+ invigilator, task
169
+ )
169
170
 
170
- self._add_answer(response=response, question=question)
171
+ self._add_answer(response=response, question=question)
171
172
 
172
- # With the answer to the question, we can now cancel any skipped questions
173
- self._cancel_skipped_questions(question)
174
- return AgentResponseDict(**response)
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=False):
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: {repr(exception)}; "
23
+ f"Attempt {attempt_number} failed with exception:" f"{exception}",
25
24
  f"now waiting {wait_time:.2f} seconds before retrying."
26
- f" Parameters: start={EDSL_BACKOFF_START_SEC}, max={EDSL_MAX_BACKOFF_SEC}, max_attempts={EDSL_MAX_ATTEMPTS}."
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