edsl 0.1.37.dev5__py3-none-any.whl → 0.1.38__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 (86) hide show
  1. edsl/Base.py +63 -34
  2. edsl/BaseDiff.py +7 -7
  3. edsl/__init__.py +2 -1
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +23 -11
  6. edsl/agents/AgentList.py +86 -23
  7. edsl/agents/Invigilator.py +18 -7
  8. edsl/agents/InvigilatorBase.py +0 -19
  9. edsl/agents/PromptConstructor.py +5 -4
  10. edsl/auto/SurveyCreatorPipeline.py +1 -1
  11. edsl/auto/utilities.py +1 -1
  12. edsl/base/Base.py +3 -13
  13. edsl/config.py +8 -0
  14. edsl/coop/coop.py +89 -19
  15. edsl/data/Cache.py +45 -17
  16. edsl/data/CacheEntry.py +8 -3
  17. edsl/data/RemoteCacheSync.py +0 -19
  18. edsl/enums.py +2 -0
  19. edsl/exceptions/agents.py +4 -0
  20. edsl/exceptions/cache.py +5 -0
  21. edsl/inference_services/GoogleService.py +7 -15
  22. edsl/inference_services/PerplexityService.py +163 -0
  23. edsl/inference_services/registry.py +2 -0
  24. edsl/jobs/Jobs.py +110 -559
  25. edsl/jobs/JobsChecks.py +147 -0
  26. edsl/jobs/JobsPrompts.py +268 -0
  27. edsl/jobs/JobsRemoteInferenceHandler.py +239 -0
  28. edsl/jobs/buckets/TokenBucket.py +3 -0
  29. edsl/jobs/interviews/Interview.py +7 -7
  30. edsl/jobs/runners/JobsRunnerAsyncio.py +156 -28
  31. edsl/jobs/runners/JobsRunnerStatus.py +194 -196
  32. edsl/jobs/tasks/TaskHistory.py +27 -19
  33. edsl/language_models/LanguageModel.py +52 -90
  34. edsl/language_models/ModelList.py +67 -14
  35. edsl/language_models/registry.py +57 -4
  36. edsl/notebooks/Notebook.py +7 -8
  37. edsl/prompts/Prompt.py +8 -3
  38. edsl/questions/QuestionBase.py +38 -30
  39. edsl/questions/QuestionBaseGenMixin.py +1 -1
  40. edsl/questions/QuestionBasePromptsMixin.py +0 -17
  41. edsl/questions/QuestionExtract.py +3 -4
  42. edsl/questions/QuestionFunctional.py +10 -3
  43. edsl/questions/derived/QuestionTopK.py +2 -0
  44. edsl/questions/question_registry.py +36 -6
  45. edsl/results/CSSParameterizer.py +108 -0
  46. edsl/results/Dataset.py +146 -15
  47. edsl/results/DatasetExportMixin.py +231 -217
  48. edsl/results/DatasetTree.py +134 -4
  49. edsl/results/Result.py +31 -16
  50. edsl/results/Results.py +159 -65
  51. edsl/results/TableDisplay.py +198 -0
  52. edsl/results/table_display.css +78 -0
  53. edsl/scenarios/FileStore.py +187 -13
  54. edsl/scenarios/Scenario.py +73 -18
  55. edsl/scenarios/ScenarioJoin.py +127 -0
  56. edsl/scenarios/ScenarioList.py +251 -76
  57. edsl/surveys/MemoryPlan.py +1 -1
  58. edsl/surveys/Rule.py +1 -5
  59. edsl/surveys/RuleCollection.py +1 -1
  60. edsl/surveys/Survey.py +25 -19
  61. edsl/surveys/SurveyFlowVisualizationMixin.py +67 -9
  62. edsl/surveys/instructions/ChangeInstruction.py +9 -7
  63. edsl/surveys/instructions/Instruction.py +21 -7
  64. edsl/templates/error_reporting/interview_details.html +3 -3
  65. edsl/templates/error_reporting/interviews.html +18 -9
  66. edsl/{conjure → utilities}/naming_utilities.py +1 -1
  67. edsl/utilities/utilities.py +15 -0
  68. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/METADATA +2 -1
  69. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/RECORD +71 -77
  70. edsl/conjure/AgentConstructionMixin.py +0 -160
  71. edsl/conjure/Conjure.py +0 -62
  72. edsl/conjure/InputData.py +0 -659
  73. edsl/conjure/InputDataCSV.py +0 -48
  74. edsl/conjure/InputDataMixinQuestionStats.py +0 -182
  75. edsl/conjure/InputDataPyRead.py +0 -91
  76. edsl/conjure/InputDataSPSS.py +0 -8
  77. edsl/conjure/InputDataStata.py +0 -8
  78. edsl/conjure/QuestionOptionMixin.py +0 -76
  79. edsl/conjure/QuestionTypeMixin.py +0 -23
  80. edsl/conjure/RawQuestion.py +0 -65
  81. edsl/conjure/SurveyResponses.py +0 -7
  82. edsl/conjure/__init__.py +0 -9
  83. edsl/conjure/examples/placeholder.txt +0 -0
  84. edsl/conjure/utilities.py +0 -201
  85. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/LICENSE +0 -0
  86. {edsl-0.1.37.dev5.dist-info → edsl-0.1.38.dist-info}/WHEEL +0 -0
@@ -41,17 +41,19 @@ from edsl.data_transfer_models import (
41
41
  AgentResponseDict,
42
42
  )
43
43
 
44
+ if TYPE_CHECKING:
45
+ from edsl.data.Cache import Cache
46
+ from edsl.scenarios.FileStore import FileStore
47
+ from edsl.questions.QuestionBase import QuestionBase
44
48
 
45
49
  from edsl.config import CONFIG
46
50
  from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
47
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
48
- from edsl.language_models.repair import repair
49
- from edsl.enums import InferenceServiceType
50
- from edsl.Base import RichPrintingMixin, PersistenceMixin
51
- from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
52
- from edsl.exceptions.language_models import LanguageModelBadResponseError
51
+ from edsl.utilities.decorators import remove_edsl_version
53
52
 
53
+ from edsl.Base import PersistenceMixin
54
+ from edsl.language_models.RegisterLanguageModelsMeta import RegisterLanguageModelsMeta
54
55
  from edsl.language_models.KeyLookup import KeyLookup
56
+ from edsl.exceptions.language_models import LanguageModelBadResponseError
55
57
 
56
58
  TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
57
59
 
@@ -116,29 +118,11 @@ def handle_key_error(func):
116
118
 
117
119
 
118
120
  class LanguageModel(
119
- RichPrintingMixin, PersistenceMixin, ABC, metaclass=RegisterLanguageModelsMeta
121
+ PersistenceMixin,
122
+ ABC,
123
+ metaclass=RegisterLanguageModelsMeta,
120
124
  ):
121
- """ABC for LLM subclasses.
122
-
123
- TODO:
124
-
125
- 1) Need better, more descriptive names for functions
126
-
127
- get_model_response_no_cache (currently called async_execute_model_call)
128
-
129
- get_model_response (currently called async_get_raw_response; uses cache & adds tracking info)
130
- Calls:
131
- - async_execute_model_call
132
- - _updated_model_response_with_tracking
133
-
134
- get_answer (currently called async_get_response)
135
- This parses out the answer block and does some error-handling.
136
- Calls:
137
- - async_get_raw_response
138
- - parse_response
139
-
140
-
141
- """
125
+ """ABC for Language Models."""
142
126
 
143
127
  _model_ = None
144
128
  key_sequence = (
@@ -196,7 +180,7 @@ class LanguageModel(
196
180
  system_prompt = "You are a helpful agent pretending to be a human."
197
181
  return self.execute_model_call(user_prompt, system_prompt)
198
182
 
199
- def set_key_lookup(self, key_lookup: KeyLookup):
183
+ def set_key_lookup(self, key_lookup: KeyLookup) -> None:
200
184
  del self._api_token
201
185
  self.key_lookup = key_lookup
202
186
 
@@ -211,10 +195,14 @@ class LanguageModel(
211
195
  def __getitem__(self, key):
212
196
  return getattr(self, key)
213
197
 
214
- def _repr_html_(self):
215
- from edsl.utilities.utilities import data_to_html
198
+ def _repr_html_(self) -> str:
199
+ d = {"model": self.model}
200
+ d.update(self.parameters)
201
+ data = [[k, v] for k, v in d.items()]
202
+ from tabulate import tabulate
216
203
 
217
- return data_to_html(self.to_dict())
204
+ table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
205
+ return f"<pre>{table}</pre>"
218
206
 
219
207
  def hello(self, verbose=False):
220
208
  """Runs a simple test to check if the model is working."""
@@ -235,7 +223,6 @@ class LanguageModel(
235
223
  This method is used to check if the model has a valid API key.
236
224
  """
237
225
  from edsl.enums import service_to_api_keyname
238
- import os
239
226
 
240
227
  if self._model_ == "test":
241
228
  return True
@@ -248,9 +235,9 @@ class LanguageModel(
248
235
  """Allow the model to be used as a key in a dictionary."""
249
236
  from edsl.utilities.utilities import dict_hash
250
237
 
251
- return dict_hash(self.to_dict())
238
+ return dict_hash(self.to_dict(add_edsl_version=False))
252
239
 
253
- def __eq__(self, other):
240
+ def __eq__(self, other) -> bool:
254
241
  """Check is two models are the same.
255
242
 
256
243
  >>> m1 = LanguageModel.example()
@@ -278,15 +265,11 @@ class LanguageModel(
278
265
  @property
279
266
  def RPM(self):
280
267
  """Model's requests-per-minute limit."""
281
- # self._set_rate_limits()
282
- # return self._safety_factor * self.__rate_limits["rpm"]
283
268
  return self._rpm
284
269
 
285
270
  @property
286
271
  def TPM(self):
287
272
  """Model's tokens-per-minute limit."""
288
- # self._set_rate_limits()
289
- # return self._safety_factor * self.__rate_limits["tpm"]
290
273
  return self._tpm
291
274
 
292
275
  @property
@@ -314,8 +297,6 @@ class LanguageModel(
314
297
  >>> LanguageModel._overide_default_parameters(passed_parameter_dict={"temperature": 0.5}, default_parameter_dict={"temperature":0.9, "max_tokens": 1000})
315
298
  {'temperature': 0.5, 'max_tokens': 1000}
316
299
  """
317
- # parameters = dict({})
318
-
319
300
  # this is the case when data is loaded from a dict after serialization
320
301
  if "parameters" in passed_parameter_dict:
321
302
  passed_parameter_dict = passed_parameter_dict["parameters"]
@@ -429,9 +410,10 @@ class LanguageModel(
429
410
  self,
430
411
  user_prompt: str,
431
412
  system_prompt: str,
432
- cache: "Cache",
413
+ cache: Cache,
433
414
  iteration: int = 0,
434
- files_list=None,
415
+ files_list: Optional[List[FileStore]] = None,
416
+ invigilator=None,
435
417
  ) -> ModelResponse:
436
418
  """Handle caching of responses.
437
419
 
@@ -455,7 +437,6 @@ class LanguageModel(
455
437
 
456
438
  if files_list:
457
439
  files_hash = "+".join([str(hash(file)) for file in files_list])
458
- # print(f"Files hash: {files_hash}")
459
440
  user_prompt_with_hashes = user_prompt + f" {files_hash}"
460
441
  else:
461
442
  user_prompt_with_hashes = user_prompt
@@ -481,9 +462,7 @@ class LanguageModel(
481
462
  "user_prompt": user_prompt,
482
463
  "system_prompt": system_prompt,
483
464
  "files_list": files_list,
484
- # **({"encoded_image": encoded_image} if encoded_image else {}),
485
465
  }
486
- # response = await f(**params)
487
466
  response = await asyncio.wait_for(f(**params), timeout=TIMEOUT)
488
467
  new_cache_key = cache.store(
489
468
  **cache_call_params, response=response
@@ -504,11 +483,9 @@ class LanguageModel(
504
483
  _async_get_intended_model_call_outcome
505
484
  )
506
485
 
507
- # get_raw_response = sync_wrapper(async_get_raw_response)
508
-
509
486
  def simple_ask(
510
487
  self,
511
- question: "QuestionBase",
488
+ question: QuestionBase,
512
489
  system_prompt="You are a helpful agent pretending to be a human.",
513
490
  top_logprobs=2,
514
491
  ):
@@ -523,9 +500,10 @@ class LanguageModel(
523
500
  self,
524
501
  user_prompt: str,
525
502
  system_prompt: str,
526
- cache: "Cache",
503
+ cache: Cache,
527
504
  iteration: int = 1,
528
- files_list: Optional[List["File"]] = None,
505
+ files_list: Optional[List[FileStore]] = None,
506
+ **kwargs,
529
507
  ) -> dict:
530
508
  """Get response, parse, and return as string.
531
509
 
@@ -543,6 +521,9 @@ class LanguageModel(
543
521
  "cache": cache,
544
522
  "files_list": files_list,
545
523
  }
524
+ if "invigilator" in kwargs:
525
+ params.update({"invigilator": kwargs["invigilator"]})
526
+
546
527
  model_inputs = ModelInputs(user_prompt=user_prompt, system_prompt=system_prompt)
547
528
  model_outputs = await self._async_get_intended_model_call_outcome(**params)
548
529
  edsl_dict = self.parse_response(model_outputs.response)
@@ -553,8 +534,6 @@ class LanguageModel(
553
534
  )
554
535
  return agent_response_dict
555
536
 
556
- # return await self._async_prepare_response(model_call_outcome, cache=cache)
557
-
558
537
  get_response = sync_wrapper(async_get_response)
559
538
 
560
539
  def cost(self, raw_response: dict[str, Any]) -> Union[float, str]:
@@ -604,21 +583,20 @@ class LanguageModel(
604
583
 
605
584
  return input_cost + output_cost
606
585
 
607
- #######################
608
- # SERIALIZATION METHODS
609
- #######################
610
- def _to_dict(self) -> dict[str, Any]:
611
- return {"model": self.model, "parameters": self.parameters}
612
-
613
- @add_edsl_version
614
- def to_dict(self) -> dict[str, Any]:
615
- """Convert instance to a dictionary.
586
+ def to_dict(self, add_edsl_version: bool = True) -> dict[str, Any]:
587
+ """Convert instance to a dictionary
616
588
 
617
589
  >>> m = LanguageModel.example()
618
590
  >>> m.to_dict()
619
591
  {'model': '...', 'parameters': {'temperature': ..., 'max_tokens': ..., 'top_p': ..., 'frequency_penalty': ..., 'presence_penalty': ..., 'logprobs': False, 'top_logprobs': ...}, 'edsl_version': '...', 'edsl_class_name': 'LanguageModel'}
620
592
  """
621
- return self._to_dict()
593
+ d = {"model": self.model, "parameters": self.parameters}
594
+ if add_edsl_version:
595
+ from edsl import __version__
596
+
597
+ d["edsl_version"] = __version__
598
+ d["edsl_class_name"] = self.__class__.__name__
599
+ return d
622
600
 
623
601
  @classmethod
624
602
  @remove_edsl_version
@@ -627,18 +605,8 @@ class LanguageModel(
627
605
  from edsl.language_models.registry import get_model_class
628
606
 
629
607
  model_class = get_model_class(data["model"])
630
- # data["use_cache"] = True
631
608
  return model_class(**data)
632
609
 
633
- #######################
634
- # DUNDER METHODS
635
- #######################
636
- def print(self):
637
- from rich import print_json
638
- import json
639
-
640
- print_json(json.dumps(self.to_dict()))
641
-
642
610
  def __repr__(self) -> str:
643
611
  """Return a string representation of the object."""
644
612
  param_string = ", ".join(
@@ -652,33 +620,21 @@ class LanguageModel(
652
620
 
653
621
  def __add__(self, other_model: Type[LanguageModel]) -> Type[LanguageModel]:
654
622
  """Combine two models into a single model (other_model takes precedence over self)."""
655
- print(
623
+ import warnings
624
+
625
+ warnings.warn(
656
626
  f"""Warning: one model is replacing another. If you want to run both models, use a single `by` e.g.,
657
627
  by(m1, m2, m3) not by(m1).by(m2).by(m3)."""
658
628
  )
659
629
  return other_model or self
660
630
 
661
- def rich_print(self):
662
- """Display an object as a table."""
663
- from rich.table import Table
664
-
665
- table = Table(title="Language Model")
666
- table.add_column("Attribute", style="bold")
667
- table.add_column("Value")
668
-
669
- to_display = self.__dict__.copy()
670
- for attr_name, attr_value in to_display.items():
671
- table.add_row(attr_name, repr(attr_value))
672
-
673
- return table
674
-
675
631
  @classmethod
676
632
  def example(
677
633
  cls,
678
634
  test_model: bool = False,
679
635
  canned_response: str = "Hello world",
680
636
  throw_exception: bool = False,
681
- ):
637
+ ) -> LanguageModel:
682
638
  """Return a default instance of the class.
683
639
 
684
640
  >>> from edsl.language_models import LanguageModel
@@ -689,11 +645,17 @@ class LanguageModel(
689
645
  >>> q = QuestionFreeText(question_text = "What is your name?", question_name = 'example')
690
646
  >>> q.by(m).run(cache = False, disable_remote_cache = True, disable_remote_inference = True).select('example').first()
691
647
  'WOWZA!'
648
+ >>> m = LanguageModel.example(test_model = True, canned_response = "WOWZA!", throw_exception = True)
649
+ >>> r = q.by(m).run(cache = False, disable_remote_cache = True, disable_remote_inference = True, print_exceptions = True)
650
+ Exception report saved to ...
651
+ Also see: ...
692
652
  """
693
653
  from edsl import Model
694
654
 
695
655
  if test_model:
696
- m = Model("test", canned_response=canned_response)
656
+ m = Model(
657
+ "test", canned_response=canned_response, throw_exception=throw_exception
658
+ )
697
659
  return m
698
660
  else:
699
661
  return Model(skip_api_key_check=True)
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Optional, List
2
2
  from collections import UserList
3
3
  from edsl import Model
4
4
 
@@ -10,6 +10,8 @@ from edsl.utilities.utilities import dict_hash
10
10
 
11
11
 
12
12
  class ModelList(Base, UserList):
13
+ __documentation__ = """https://docs.expectedparrot.com/en/latest/language_models.html#module-edsl.language_models.ModelList"""
14
+
13
15
  def __init__(self, data: Optional[list] = None):
14
16
  """Initialize the ScenarioList class.
15
17
 
@@ -37,6 +39,9 @@ class ModelList(Base, UserList):
37
39
  def __repr__(self):
38
40
  return f"ModelList({super().__repr__()})"
39
41
 
42
+ def _summary(self):
43
+ return {"EDSL Class": "ModelList", "Number of Models": len(self)}
44
+
40
45
  def __hash__(self):
41
46
  """Return a hash of the ModelList. This is used for comparison of ModelLists.
42
47
 
@@ -46,14 +51,71 @@ class ModelList(Base, UserList):
46
51
  """
47
52
  from edsl.utilities.utilities import dict_hash
48
53
 
49
- return dict_hash(self._to_dict(sort=True))
54
+ return dict_hash(self.to_dict(sort=True, add_edsl_version=False))
55
+
56
+ def to_scenario_list(self):
57
+ from edsl import ScenarioList, Scenario
58
+
59
+ sl = ScenarioList()
60
+ for model in self:
61
+ d = {"model": model.model}
62
+ d.update(model.parameters)
63
+ sl.append(Scenario(d))
64
+ return sl
65
+
66
+ def tree(self, node_list: Optional[List[str]] = None):
67
+ return self.to_scenario_list().tree(node_list)
68
+
69
+ def table(
70
+ self,
71
+ *fields,
72
+ tablefmt: Optional[str] = None,
73
+ pretty_labels: Optional[dict] = None,
74
+ ):
75
+ """
76
+ >>> ModelList.example().table("model")
77
+ model
78
+ -------
79
+ gpt-4o
80
+ gpt-4o
81
+ gpt-4o
82
+ """
83
+ return (
84
+ self.to_scenario_list()
85
+ .to_dataset()
86
+ .table(*fields, tablefmt=tablefmt, pretty_labels=pretty_labels)
87
+ )
50
88
 
51
- def _to_dict(self, sort=False):
89
+ def to_list(self):
90
+ return self.to_scenario_list().to_list()
91
+
92
+ def to_dict(self, sort=False, add_edsl_version=True):
52
93
  if sort:
53
94
  model_list = sorted([model for model in self], key=lambda x: hash(x))
54
- return {"models": [model._to_dict() for model in model_list]}
95
+ d = {
96
+ "models": [
97
+ model.to_dict(add_edsl_version=add_edsl_version)
98
+ for model in model_list
99
+ ]
100
+ }
55
101
  else:
56
- return {"models": [model._to_dict() for model in self]}
102
+ d = {
103
+ "models": [
104
+ model.to_dict(add_edsl_version=add_edsl_version) for model in self
105
+ ]
106
+ }
107
+ if add_edsl_version:
108
+ from edsl import __version__
109
+
110
+ d["edsl_version"] = __version__
111
+ d["edsl_class_name"] = "ModelList"
112
+
113
+ return d
114
+
115
+ def _repr_html_(self):
116
+ """Return an HTML representation of the ModelList."""
117
+ footer = f"<a href={self.__documentation__}>(docs)</a>"
118
+ return str(self.summary(format="html")) + footer
57
119
 
58
120
  @classmethod
59
121
  def from_names(self, *args, **kwargs):
@@ -62,15 +124,6 @@ class ModelList(Base, UserList):
62
124
  args = args[0]
63
125
  return ModelList([Model(model_name, **kwargs) for model_name in args])
64
126
 
65
- @add_edsl_version
66
- def to_dict(self):
67
- """
68
- Convert the ModelList to a dictionary.
69
- >>> ModelList.example().to_dict()
70
- {'models': [...], 'edsl_version': '...', 'edsl_class_name': 'ModelList'}
71
- """
72
- return self._to_dict()
73
-
74
127
  @classmethod
75
128
  @remove_edsl_version
76
129
  def from_dict(cls, data):
@@ -7,6 +7,49 @@ from edsl.config import CONFIG
7
7
  # else:
8
8
  # default_model = CONFIG.get("EDSL_DEFAULT_MODEL")
9
9
 
10
+ from collections import UserList
11
+
12
+
13
+ class PrettyList(UserList):
14
+ def __init__(self, data=None, columns=None):
15
+ super().__init__(data)
16
+ self.columns = columns
17
+
18
+ def _repr_html_(self):
19
+ if isinstance(self[0], list) or isinstance(self[0], tuple):
20
+ num_cols = len(self[0])
21
+ else:
22
+ num_cols = 1
23
+
24
+ if self.columns:
25
+ columns = self.columns
26
+ else:
27
+ columns = list(range(num_cols))
28
+
29
+ if num_cols > 1:
30
+ return (
31
+ "<pre><table>"
32
+ + "".join(["<th>" + str(column) + "</th>" for column in columns])
33
+ + "".join(
34
+ [
35
+ "<tr>"
36
+ + "".join(["<td>" + str(x) + "</td>" for x in row])
37
+ + "</tr>"
38
+ for row in self
39
+ ]
40
+ )
41
+ + "</table></pre>"
42
+ )
43
+ else:
44
+ return (
45
+ "<pre><table>"
46
+ + "".join(["<th>" + str(index) + "</th>" for index in columns])
47
+ + "".join(
48
+ ["<tr>" + "<td>" + str(row) + "</td>" + "</tr>" for row in self]
49
+ )
50
+ + "</table></pre>"
51
+ )
52
+
10
53
 
11
54
  def get_model_class(model_name, registry=None):
12
55
  from edsl.inference_services.registry import default
@@ -82,17 +125,27 @@ class Model(metaclass=Meta):
82
125
 
83
126
  if search_term is None:
84
127
  if name_only:
85
- return [m[0] for m in full_list]
128
+ return PrettyList(
129
+ [m[0] for m in full_list],
130
+ columns=["Model Name", "Service Name", "Code"],
131
+ )
86
132
  else:
87
- return full_list
133
+ return PrettyList(
134
+ full_list, columns=["Model Name", "Service Name", "Code"]
135
+ )
88
136
  else:
89
137
  filtered_results = [
90
138
  m for m in full_list if search_term in m[0] or search_term in m[1]
91
139
  ]
92
140
  if name_only:
93
- return [m[0] for m in filtered_results]
141
+ return PrettyList(
142
+ [m[0] for m in filtered_results],
143
+ columns=["Model Name", "Service Name", "Code"],
144
+ )
94
145
  else:
95
- return filtered_results
146
+ return PrettyList(
147
+ filtered_results, columns=["Model Name", "Service Name", "Code"]
148
+ )
96
149
 
97
150
  @classmethod
98
151
  def check_models(cls, verbose=False):
@@ -102,18 +102,17 @@ class Notebook(Base):
102
102
 
103
103
  return dict_hash(self.data["cells"])
104
104
 
105
- def _to_dict(self) -> dict:
105
+ def to_dict(self, add_edsl_version=False) -> dict:
106
106
  """
107
107
  Serialize to a dictionary.
108
108
  """
109
- return {"name": self.name, "data": self.data}
109
+ d = {"name": self.name, "data": self.data}
110
+ if add_edsl_version:
111
+ from edsl import __version__
110
112
 
111
- @add_edsl_version
112
- def to_dict(self) -> dict:
113
- """
114
- Convert a Notebook to a dictionary.
115
- """
116
- return self._to_dict()
113
+ d["edsl_version"] = __version__
114
+ d["edsl_class_name"] = self.__class__.__name__
115
+ return d
117
116
 
118
117
  @classmethod
119
118
  @remove_edsl_version
edsl/prompts/Prompt.py CHANGED
@@ -29,9 +29,14 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
29
29
 
30
30
  def _repr_html_(self):
31
31
  """Return an HTML representation of the Prompt."""
32
- from edsl.utilities.utilities import data_to_html
33
-
34
- return data_to_html(self.to_dict())
32
+ # from edsl.utilities.utilities import data_to_html
33
+ # return data_to_html(self.to_dict())
34
+ d = self.to_dict()
35
+ data = [[k, v] for k, v in d.items()]
36
+ from tabulate import tabulate
37
+
38
+ table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
39
+ return f"<pre>{table}</pre>"
35
40
 
36
41
  def __len__(self):
37
42
  """Return the length of the prompt text."""
@@ -135,7 +135,7 @@ class QuestionBase(
135
135
  """
136
136
  from edsl.utilities.utilities import dict_hash
137
137
 
138
- return dict_hash(self._to_dict())
138
+ return dict_hash(self.to_dict(add_edsl_version=False))
139
139
 
140
140
  @property
141
141
  def data(self) -> dict:
@@ -147,13 +147,15 @@ class QuestionBase(
147
147
  """
148
148
  exclude_list = [
149
149
  "question_type",
150
- "_include_comment",
150
+ # "_include_comment",
151
151
  "_fake_data_factory",
152
- "_use_code",
152
+ # "_use_code",
153
153
  "_model_instructions",
154
154
  ]
155
155
  only_if_not_na_list = ["_answering_instructions", "_question_presentation"]
156
156
 
157
+ only_if_not_default_list = {"_include_comment": True, "_use_code": False}
158
+
157
159
  def ok(key, value):
158
160
  if not key.startswith("_"):
159
161
  return False
@@ -161,6 +163,12 @@ class QuestionBase(
161
163
  return False
162
164
  if key in only_if_not_na_list and value is None:
163
165
  return False
166
+ if (
167
+ key in only_if_not_default_list
168
+ and value == only_if_not_default_list[key]
169
+ ):
170
+ return False
171
+
164
172
  return True
165
173
 
166
174
  candidate_data = {
@@ -175,25 +183,22 @@ class QuestionBase(
175
183
 
176
184
  return candidate_data
177
185
 
178
- def _to_dict(self):
186
+ def to_dict(self, add_edsl_version=True):
179
187
  """Convert the question to a dictionary that includes the question type (used in deserialization).
180
188
 
181
- >>> from edsl import QuestionFreeText as Q; Q.example()._to_dict()
189
+ >>> from edsl import QuestionFreeText as Q; Q.example().to_dict(add_edsl_version = False)
182
190
  {'question_name': 'how_are_you', 'question_text': 'How are you?', 'question_type': 'free_text'}
183
191
  """
184
192
  candidate_data = self.data.copy()
185
193
  candidate_data["question_type"] = self.question_type
186
- return {
187
- key: value for key, value in candidate_data.items() if value is not None
188
- }
194
+ d = {key: value for key, value in candidate_data.items() if value is not None}
195
+ if add_edsl_version:
196
+ from edsl import __version__
189
197
 
190
- @add_edsl_version
191
- def to_dict(self) -> dict[str, Any]:
192
- """Convert the question to a dictionary that includes the question type (used in deserialization).
193
- >>> from edsl import QuestionFreeText as Q; Q.example().to_dict()
194
- {'question_name': 'how_are_you', 'question_text': 'How are you?', 'question_type': 'free_text', 'edsl_version': '...'}
195
- """
196
- return self._to_dict()
198
+ d["edsl_version"] = __version__
199
+ d["edsl_class_name"] = "QuestionBase"
200
+
201
+ return d
197
202
 
198
203
  @classmethod
199
204
  @remove_edsl_version
@@ -259,12 +264,9 @@ class QuestionBase(
259
264
  >>> m.execute_model_call("", "")
260
265
  {'message': [{'text': "Yo, what's up?"}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
261
266
  >>> Q.run_example(show_answer = True, model = m, disable_remote_cache = True, disable_remote_inference = True)
262
- ┏━━━━━━━━━━━━━━━━┓
263
- ┃ answer ┃
264
- .how_are_you ┃
265
- ┡━━━━━━━━━━━━━━━━┩
266
- │ Yo, what's up? │
267
- └────────────────┘
267
+ answer.how_are_you
268
+ --------------------
269
+ Yo, what's up?
268
270
  """
269
271
  if model is None:
270
272
  from edsl import Model
@@ -280,7 +282,7 @@ class QuestionBase(
280
282
  )
281
283
  )
282
284
  if show_answer:
283
- results.select("answer.*").print()
285
+ return results.select("answer.*").print()
284
286
  else:
285
287
  return results
286
288
 
@@ -358,16 +360,22 @@ class QuestionBase(
358
360
 
359
361
  # region: Magic methods
360
362
  def _repr_html_(self):
361
- from edsl.utilities.utilities import data_to_html
363
+ # from edsl.utilities.utilities import data_to_html
362
364
 
363
- data = self.to_dict()
364
- try:
365
- _ = data.pop("edsl_version")
366
- _ = data.pop("edsl_class_name")
367
- except KeyError:
368
- print("Serialized question lacks edsl version, but is should have it.")
365
+ data = self.to_dict(add_edsl_version=False)
366
+ # keys = list(data.keys())
367
+ # values = list(data.values())
368
+ from tabulate import tabulate
369
+
370
+ return tabulate(data.items(), headers=["keys", "values"], tablefmt="html")
371
+
372
+ # try:
373
+ # _ = data.pop("edsl_version")
374
+ # _ = data.pop("edsl_class_name")
375
+ # except KeyError:
376
+ # print("Serialized question lacks edsl version, but is should have it.")
369
377
 
370
- return data_to_html(data)
378
+ # return data_to_html(data)
371
379
 
372
380
  def __getitem__(self, key: str) -> Any:
373
381
  """Get an attribute of the question so it can be treated like a dictionary.