edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 (212) hide show
  1. edsl/Base.py +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
edsl/data/CacheEntry.py CHANGED
@@ -5,8 +5,12 @@ import hashlib
5
5
  from typing import Optional
6
6
  from uuid import uuid4
7
7
 
8
+ from edsl.utilities.decorators import remove_edsl_version
8
9
 
9
- class CacheEntry:
10
+ from edsl.Base import RepresentationMixin
11
+
12
+
13
+ class CacheEntry(RepresentationMixin):
10
14
  """
11
15
  A Class to represent a cache entry.
12
16
  """
@@ -78,11 +82,11 @@ class CacheEntry:
78
82
  d = {k: value for k, value in self.__dict__.items() if k in self.key_fields}
79
83
  return self.gen_key(**d)
80
84
 
81
- def to_dict(self) -> dict:
85
+ def to_dict(self, add_edsl_version=True) -> dict:
82
86
  """
83
87
  Returns a dictionary representation of a CacheEntry.
84
88
  """
85
- return {
89
+ d = {
86
90
  "model": self.model,
87
91
  "parameters": self.parameters,
88
92
  "system_prompt": self.system_prompt,
@@ -91,19 +95,12 @@ class CacheEntry:
91
95
  "iteration": self.iteration,
92
96
  "timestamp": self.timestamp,
93
97
  }
98
+ # if add_edsl_version:
99
+ # from edsl import __version__
94
100
 
95
- def _repr_html_(self) -> str:
96
- """
97
- Returns an HTML representation of a CacheEntry.
98
- """
99
- # from edsl.utilities.utilities import data_to_html
100
- # return data_to_html(self.to_dict())
101
- d = self.to_dict()
102
- data = [[k, v] for k, v in d.items()]
103
- from tabulate import tabulate
104
-
105
- table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
106
- return f"<pre>{table}</pre>"
101
+ # d["edsl_version"] = __version__
102
+ # d["edsl_class_name"] = self.__class__.__name__
103
+ return d
107
104
 
108
105
  def keys(self):
109
106
  return list(self.to_dict().keys())
edsl/data/CacheHandler.py CHANGED
@@ -3,19 +3,19 @@ import ast
3
3
  import json
4
4
  import os
5
5
  import shutil
6
- import sqlite3
7
- from edsl.config import CONFIG
8
- from edsl.data.Cache import Cache
9
- from edsl.data.CacheEntry import CacheEntry
10
- from edsl.data.SQLiteDict import SQLiteDict
6
+ from typing import TYPE_CHECKING
11
7
 
12
- from edsl.config import CONFIG
8
+ if TYPE_CHECKING:
9
+ from edsl.data.Cache import Cache
10
+ from edsl.data.CacheEntry import CacheEntry
13
11
 
14
12
 
15
- def set_session_cache(cache: Cache) -> None:
13
+ def set_session_cache(cache: "Cache") -> None:
16
14
  """
17
15
  Set the session cache.
18
16
  """
17
+ from edsl.config import CONFIG
18
+
19
19
  CONFIG.EDSL_SESSION_CACHE = cache
20
20
 
21
21
 
@@ -23,6 +23,8 @@ def unset_session_cache() -> None:
23
23
  """
24
24
  Unset the session cache.
25
25
  """
26
+ from edsl.config import CONFIG
27
+
26
28
  if hasattr(CONFIG, "EDSL_SESSION_CACHE"):
27
29
  del CONFIG.EDSL_SESSION_CACHE
28
30
 
@@ -32,7 +34,11 @@ class CacheHandler:
32
34
  This CacheHandler figures out what caches are available and does migrations, as needed.
33
35
  """
34
36
 
35
- CACHE_PATH = CONFIG.get("EDSL_DATABASE_PATH")
37
+ @property
38
+ def CACHE_PATH(self):
39
+ from edsl.config import CONFIG
40
+
41
+ return CONFIG.get("EDSL_DATABASE_PATH")
36
42
 
37
43
  def __init__(self, test: bool = False):
38
44
  self.test = test
@@ -52,16 +58,22 @@ class CacheHandler:
52
58
  if notify:
53
59
  print(f"Created cache directory: {dir_path}")
54
60
 
55
- def gen_cache(self) -> Cache:
61
+ def gen_cache(self) -> "Cache":
56
62
  """
57
63
  Generate a Cache object.
58
64
  """
65
+ from edsl.data.Cache import Cache
66
+
59
67
  if self.test:
60
68
  return Cache(data={})
61
69
 
70
+ from edsl.config import CONFIG
71
+
62
72
  if hasattr(CONFIG, "EDSL_SESSION_CACHE"):
63
73
  return CONFIG.EDSL_SESSION_CACHE
64
74
 
75
+ from edsl.data.SQLiteDict import SQLiteDict
76
+
65
77
  cache = Cache(data=SQLiteDict(self.CACHE_PATH))
66
78
  return cache
67
79
 
@@ -76,6 +88,8 @@ class CacheHandler:
76
88
  if not os.path.exists(os.path.join(os.getcwd(), path)):
77
89
  return old_data
78
90
  try:
91
+ import sqlite3
92
+
79
93
  conn = sqlite3.connect(path)
80
94
  with conn:
81
95
  cur = conn.cursor()
@@ -108,6 +122,8 @@ class CacheHandler:
108
122
  entry_dict["user_prompt"] = entry_dict.pop("prompt")
109
123
  parameters = entry_dict["parameters"]
110
124
  entry_dict["parameters"] = ast.literal_eval(parameters)
125
+ from edsl.data.CacheEntry import CacheEntry
126
+
111
127
  entry = CacheEntry(**entry_dict)
112
128
  return entry
113
129
 
@@ -117,7 +133,7 @@ class CacheHandler:
117
133
  ###############
118
134
  # NOT IN USE
119
135
  ###############
120
- def from_sqlite(uri="new_edsl_cache.db") -> dict[str, CacheEntry]:
136
+ def from_sqlite(uri="new_edsl_cache.db") -> dict[str, "CacheEntry"]:
121
137
  """
122
138
  Read in a new-style sqlite cache and return a dictionary of dictionaries.
123
139
  """
@@ -131,7 +147,7 @@ class CacheHandler:
131
147
  newdata[entry.key] = entry
132
148
  return newdata
133
149
 
134
- def from_jsonl(filename="edsl_cache.jsonl") -> dict[str, CacheEntry]:
150
+ def from_jsonl(filename="edsl_cache.jsonl") -> dict[str, "CacheEntry"]:
135
151
  """Read in a jsonl file and return a dictionary of CacheEntry objects."""
136
152
  with open(filename, "a+") as f:
137
153
  f.seek(0)
@@ -146,4 +162,7 @@ class CacheHandler:
146
162
 
147
163
 
148
164
  if __name__ == "__main__":
149
- ch = CacheHandler()
165
+ # ch = CacheHandler()
166
+ import doctest
167
+
168
+ doctest.testmod()
@@ -1,71 +1,166 @@
1
- class RemoteCacheSync:
1
+ from typing import List, Dict, Any, Optional, TYPE_CHECKING, Callable
2
+ from dataclasses import dataclass
3
+ from contextlib import AbstractContextManager
4
+ from collections import UserList
5
+
6
+ if TYPE_CHECKING:
7
+ from .Cache import Cache
8
+ from edsl.coop.coop import Coop
9
+ from .CacheEntry import CacheEntry
10
+
11
+ from logging import Logger
12
+
13
+
14
+ class CacheKeyList(UserList):
15
+ def __init__(self, data: List[str]):
16
+ super().__init__(data)
17
+ self.data = data
18
+
19
+ def __repr__(self):
20
+ import reprlib
21
+
22
+ keys_repr = reprlib.repr(self.data)
23
+ return f"CacheKeyList({keys_repr})"
24
+
25
+
26
+ class CacheEntriesList(UserList):
27
+ def __init__(self, data: List["CacheEntry"]):
28
+ super().__init__(data)
29
+ self.data = data
30
+
31
+ def __repr__(self):
32
+ import reprlib
33
+
34
+ entries_repr = reprlib.repr(self.data)
35
+ return f"CacheEntries({entries_repr})"
36
+
37
+ def to_cache(self) -> "Cache":
38
+ from edsl.data.Cache import Cache
39
+
40
+ return Cache({entry.key: entry for entry in self.data})
41
+
42
+
43
+ @dataclass
44
+ class CacheDifference:
45
+ client_missing_entries: CacheEntriesList
46
+ server_missing_keys: List[str]
47
+
48
+ def __repr__(self):
49
+ """Returns a string representation of the CacheDifference object."""
50
+ import reprlib
51
+
52
+ missing_entries_repr = reprlib.repr(self.client_missing_entries)
53
+ missing_keys_repr = reprlib.repr(self.server_missing_keys)
54
+ return f"CacheDifference(client_missing_entries={missing_entries_repr}, server_missing_keys={missing_keys_repr})"
55
+
56
+
57
+ class RemoteCacheSync(AbstractContextManager):
58
+ """Synchronizes a local cache with a remote cache.
59
+
60
+ Handles bidirectional synchronization:
61
+ - Downloads missing entries from remote to local cache
62
+ - Uploads new local entries to remote cache
63
+ """
64
+
2
65
  def __init__(
3
- self, coop, cache, output_func, remote_cache=True, remote_cache_description=""
66
+ self,
67
+ coop: "Coop",
68
+ cache: "Cache",
69
+ output_func: Callable,
70
+ remote_cache: bool = True,
71
+ remote_cache_description: str = "",
4
72
  ):
73
+ """
74
+ Initializes a RemoteCacheSync object.
75
+
76
+ :param coop: Coop object for interacting with the remote cache
77
+ :param cache: Cache object for local cache
78
+ :param output_func: Function for outputting messages
79
+ :param remote_cache: Whether to enable remote cache synchronization
80
+ :param remote_cache_description: Description for remote cache entries
81
+
82
+ """
5
83
  self.coop = coop
6
84
  self.cache = cache
7
85
  self._output = output_func
8
- self.remote_cache = remote_cache
9
- self.old_entry_keys = []
10
- self.new_cache_entries = []
86
+ self.remote_cache_enabled = remote_cache
11
87
  self.remote_cache_description = remote_cache_description
88
+ self.initial_cache_keys = []
12
89
 
13
- def __enter__(self):
14
- if self.remote_cache:
90
+ def __enter__(self) -> "RemoteCacheSync":
91
+ if self.remote_cache_enabled:
15
92
  self._sync_from_remote()
16
- self.old_entry_keys = list(self.cache.keys())
93
+ self.initial_cache_keys = list(self.cache.keys())
17
94
  return self
18
95
 
19
96
  def __exit__(self, exc_type, exc_value, traceback):
20
- if self.remote_cache:
97
+ if self.remote_cache_enabled:
21
98
  self._sync_to_remote()
22
99
  return False # Propagate exceptions
23
100
 
24
- def _sync_from_remote(self):
25
- cache_difference = self.coop.remote_cache_get_diff(self.cache.keys())
26
- client_missing_cacheentries = cache_difference.get(
27
- "client_missing_cacheentries", []
101
+ def _get_cache_difference(self) -> CacheDifference:
102
+ """Retrieves differences between local and remote caches."""
103
+ diff = self.coop.remote_cache_get_diff(self.cache.keys())
104
+ return CacheDifference(
105
+ client_missing_entries=diff.get("client_missing_cacheentries", []),
106
+ server_missing_keys=diff.get("server_missing_cacheentry_keys", []),
28
107
  )
29
- missing_entry_count = len(client_missing_cacheentries)
30
108
 
31
- if missing_entry_count > 0:
32
- self._output(
33
- f"Updating local cache with {missing_entry_count:,} new "
34
- f"{'entry' if missing_entry_count == 1 else 'entries'} from remote..."
35
- )
36
- self.cache.add_from_dict(
37
- {entry.key: entry for entry in client_missing_cacheentries}
38
- )
39
- self._output("Local cache updated!")
40
- else:
109
+ def _sync_from_remote(self) -> None:
110
+ """Downloads missing entries from remote cache to local cache."""
111
+ diff: CacheDifference = self._get_cache_difference()
112
+ missing_count = len(diff.client_missing_entries)
113
+
114
+ if missing_count == 0:
41
115
  self._output("No new entries to add to local cache.")
116
+ return
42
117
 
43
- def _sync_to_remote(self):
44
- cache_difference = self.coop.remote_cache_get_diff(self.cache.keys())
45
- server_missing_cacheentry_keys = cache_difference.get(
46
- "server_missing_cacheentry_keys", []
118
+ self._output(
119
+ f"Updating local cache with {missing_count:,} new "
120
+ f"{'entry' if missing_count == 1 else 'entries'} from remote..."
47
121
  )
48
- server_missing_cacheentries = [
49
- entry
50
- for key in server_missing_cacheentry_keys
51
- if (entry := self.cache.data.get(key)) is not None
52
- ]
53
-
54
- new_cache_entries = [
55
- entry
56
- for entry in self.cache.values()
57
- if entry.key not in self.old_entry_keys
58
- ]
59
- server_missing_cacheentries.extend(new_cache_entries)
60
- new_entry_count = len(server_missing_cacheentries)
61
-
62
- if new_entry_count > 0:
122
+
123
+ self.cache.add_from_dict(
124
+ {entry.key: entry for entry in diff.client_missing_entries}
125
+ )
126
+ self._output("Local cache updated!")
127
+
128
+ def _get_entries_to_upload(self, diff: CacheDifference) -> CacheEntriesList:
129
+ """Determines which entries need to be uploaded to remote cache."""
130
+ # Get entries for keys missing from server
131
+ server_missing_entries = CacheEntriesList(
132
+ [
133
+ entry
134
+ for key in diff.server_missing_keys
135
+ if (entry := self.cache.data.get(key)) is not None
136
+ ]
137
+ )
138
+
139
+ # Get newly added entries since sync started
140
+ new_entries = CacheEntriesList(
141
+ [
142
+ entry
143
+ for entry in self.cache.values()
144
+ if entry.key not in self.initial_cache_keys
145
+ ]
146
+ )
147
+
148
+ return server_missing_entries + new_entries
149
+
150
+ def _sync_to_remote(self) -> None:
151
+ """Uploads new local entries to remote cache."""
152
+ diff: CacheDifference = self._get_cache_difference()
153
+ entries_to_upload: CacheEntriesList = self._get_entries_to_upload(diff)
154
+ upload_count = len(entries_to_upload)
155
+
156
+ if upload_count > 0:
63
157
  self._output(
64
- f"Updating remote cache with {new_entry_count:,} new "
65
- f"{'entry' if new_entry_count == 1 else 'entries'}..."
158
+ f"Updating remote cache with {upload_count:,} new "
159
+ f"{'entry' if upload_count == 1 else 'entries'}..."
66
160
  )
161
+
67
162
  self.coop.remote_cache_create_many(
68
- server_missing_cacheentries,
163
+ entries_to_upload,
69
164
  visibility="private",
70
165
  description=self.remote_cache_description,
71
166
  )
@@ -76,3 +171,16 @@ class RemoteCacheSync:
76
171
  self._output(
77
172
  f"There are {len(self.cache.keys()):,} entries in the local cache."
78
173
  )
174
+
175
+
176
+ if __name__ == "__main__":
177
+ import doctest
178
+
179
+ doctest.testmod()
180
+
181
+ from edsl.coop.coop import Coop
182
+ from edsl.data.Cache import Cache
183
+ from edsl.data.CacheEntry import CacheEntry
184
+
185
+ r = RemoteCacheSync(Coop(), Cache(), print)
186
+ diff = r._get_cache_difference()
edsl/data/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
- from edsl.data.CacheEntry import CacheEntry
2
- from edsl.data.SQLiteDict import SQLiteDict
1
+ # from edsl.data.CacheEntry import CacheEntry
2
+ # from edsl.data.SQLiteDict import SQLiteDict
3
3
  from edsl.data.Cache import Cache
4
- from edsl.data.CacheHandler import CacheHandler
4
+
5
+ # from edsl.data.CacheHandler import CacheHandler
@@ -1,6 +1,5 @@
1
1
  from typing import NamedTuple, Dict, List, Optional, Any
2
2
  from dataclasses import dataclass, fields
3
- import reprlib
4
3
 
5
4
 
6
5
  class ModelInputs(NamedTuple):
@@ -56,6 +55,8 @@ class ImageInfo:
56
55
  encoded_image: str
57
56
 
58
57
  def __repr__(self):
58
+ import reprlib
59
+
59
60
  reprlib_instance = reprlib.Repr()
60
61
  reprlib_instance.maxstring = 30 # Limit the string length for the encoded image
61
62
 
edsl/enums.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Enums for the different types of questions, language models, and inference services."""
2
2
 
3
3
  from enum import Enum
4
+ from typing import Literal
4
5
 
5
6
 
6
7
  class EnumWithChecks(Enum):
@@ -67,6 +68,32 @@ class InferenceServiceType(EnumWithChecks):
67
68
  PERPLEXITY = "perplexity"
68
69
 
69
70
 
71
+ # unavoidable violation of the DRY principle but it is necessary
72
+ # checked w/ a unit test to make sure consistent with services in enums.py
73
+ InferenceServiceLiteral = Literal[
74
+ "bedrock",
75
+ "deep_infra",
76
+ "replicate",
77
+ "openai",
78
+ "google",
79
+ "test",
80
+ "anthropic",
81
+ "groq",
82
+ "azure",
83
+ "ollama",
84
+ "mistral",
85
+ "together",
86
+ "perplexity",
87
+ ]
88
+
89
+ available_models_urls = {
90
+ "anthropic": "https://docs.anthropic.com/en/docs/about-claude/models",
91
+ "openai": "https://platform.openai.com/docs/models/gp",
92
+ "groq": "https://console.groq.com/docs/models",
93
+ "google": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models",
94
+ }
95
+
96
+
70
97
  service_to_api_keyname = {
71
98
  InferenceServiceType.BEDROCK.value: "TBD",
72
99
  InferenceServiceType.DEEP_INFRA.value: "DEEP_INFRA_API_KEY",
@@ -1,54 +1,54 @@
1
- from .agents import (
2
- # AgentAttributeLookupCallbackError,
3
- AgentCombinationError,
4
- # AgentLacksLLMError,
5
- # AgentRespondedWithBadJSONError,
6
- )
7
- from .configuration import (
8
- InvalidEnvironmentVariableError,
9
- MissingEnvironmentVariableError,
10
- )
11
- from .data import (
12
- DatabaseConnectionError,
13
- DatabaseCRUDError,
14
- DatabaseIntegrityError,
15
- )
1
+ # from .agents import (
2
+ # # AgentAttributeLookupCallbackError,
3
+ # AgentCombinationError,
4
+ # # AgentLacksLLMError,
5
+ # # AgentRespondedWithBadJSONError,
6
+ # )
7
+ # from .configuration import (
8
+ # InvalidEnvironmentVariableError,
9
+ # MissingEnvironmentVariableError,
10
+ # )
11
+ # from .data import (
12
+ # DatabaseConnectionError,
13
+ # DatabaseCRUDError,
14
+ # DatabaseIntegrityError,
15
+ # )
16
16
 
17
- from .scenarios import (
18
- ScenarioError,
19
- )
17
+ # from .scenarios import (
18
+ # ScenarioError,
19
+ # )
20
20
 
21
- from .general import MissingAPIKeyError
21
+ # from .general import MissingAPIKeyError
22
22
 
23
- from .jobs import JobsRunError, InterviewErrorPriorTaskCanceled, InterviewTimeoutError
23
+ # from .jobs import JobsRunError, InterviewErrorPriorTaskCanceled, InterviewTimeoutError
24
24
 
25
- from .language_models import (
26
- LanguageModelResponseNotJSONError,
27
- LanguageModelMissingAttributeError,
28
- LanguageModelAttributeTypeError,
29
- LanguageModelDoNotAddError,
30
- )
31
- from .questions import (
32
- QuestionAnswerValidationError,
33
- QuestionAttributeMissing,
34
- QuestionCreationValidationError,
35
- QuestionResponseValidationError,
36
- QuestionSerializationError,
37
- QuestionScenarioRenderError,
38
- )
39
- from .results import (
40
- ResultsBadMutationstringError,
41
- ResultsColumnNotFoundError,
42
- ResultsInvalidNameError,
43
- ResultsMutateError,
44
- )
45
- from .surveys import (
46
- SurveyCreationError,
47
- SurveyHasNoRulesError,
48
- SurveyRuleCannotEvaluateError,
49
- SurveyRuleCollectionHasNoRulesAtNodeError,
50
- SurveyRuleReferenceInRuleToUnknownQuestionError,
51
- SurveyRuleRefersToFutureStateError,
52
- SurveyRuleSendsYouBackwardsError,
53
- SurveyRuleSkipLogicSyntaxError,
54
- )
25
+ # from .language_models import (
26
+ # LanguageModelResponseNotJSONError,
27
+ # LanguageModelMissingAttributeError,
28
+ # LanguageModelAttributeTypeError,
29
+ # LanguageModelDoNotAddError,
30
+ # )
31
+ # from .questions import (
32
+ # QuestionAnswerValidationError,
33
+ # QuestionAttributeMissing,
34
+ # QuestionCreationValidationError,
35
+ # QuestionResponseValidationError,
36
+ # QuestionSerializationError,
37
+ # QuestionScenarioRenderError,
38
+ # )
39
+ # from .results import (
40
+ # ResultsBadMutationstringError,
41
+ # ResultsColumnNotFoundError,
42
+ # ResultsInvalidNameError,
43
+ # ResultsMutateError,
44
+ # )
45
+ # from .surveys import (
46
+ # SurveyCreationError,
47
+ # SurveyHasNoRulesError,
48
+ # SurveyRuleCannotEvaluateError,
49
+ # SurveyRuleCollectionHasNoRulesAtNodeError,
50
+ # SurveyRuleReferenceInRuleToUnknownQuestionError,
51
+ # SurveyRuleRefersToFutureStateError,
52
+ # SurveyRuleSendsYouBackwardsError,
53
+ # SurveyRuleSkipLogicSyntaxError,
54
+ # )
edsl/exceptions/agents.py CHANGED
@@ -1,6 +1,18 @@
1
1
  from edsl.exceptions.BaseException import BaseException
2
2
 
3
3
 
4
+ # from edsl.utilities.utilities import is_notebook
5
+
6
+ # from IPython.core.error import UsageError
7
+
8
+ # class AgentListErrorAlternative(UsageError):
9
+ # def __init__(self, message):
10
+ # super().__init__(message)
11
+
12
+ import sys
13
+ from edsl.utilities.is_notebook import is_notebook
14
+
15
+
4
16
  class AgentListError(BaseException):
5
17
  relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#agent-lists"
6
18
 
@@ -0,0 +1,5 @@
1
+ from edsl.exceptions.BaseException import BaseException
2
+
3
+
4
+ class InferenceServiceError(BaseException):
5
+ relevant_doc = "https://docs.expectedparrot.com/"
@@ -1,6 +1,6 @@
1
1
  from typing import Any, SupportsIndex
2
- from jinja2 import Template
3
2
  import json
3
+ from pydantic import ValidationError
4
4
 
5
5
 
6
6
  class QuestionErrors(Exception):
@@ -20,17 +20,35 @@ class QuestionAnswerValidationError(QuestionErrors):
20
20
  For example, if the question is a multiple choice question, the answer should be drawn from the list of options provided.
21
21
  """
22
22
 
23
- def __init__(self, message="Invalid answer.", data=None, model=None):
23
+ def __init__(
24
+ self,
25
+ message="Invalid answer.",
26
+ pydantic_error: ValidationError = None,
27
+ data: dict = None,
28
+ model=None,
29
+ ):
24
30
  self.message = message
31
+ self.pydantic_error = pydantic_error
25
32
  self.data = data
26
33
  self.model = model
27
34
  super().__init__(self.message)
28
35
 
29
36
  def __str__(self):
30
- return f"""{repr(self)}
31
- Data being validated: {self.data}
32
- Pydnantic Model: {self.model}.
33
- Reported error: {self.message}."""
37
+ if isinstance(self.message, ValidationError):
38
+ # If it's a ValidationError, just return the core error message
39
+ return str(self.message)
40
+ elif hasattr(self.message, "errors"):
41
+ # Handle the case where it's already been converted to a string but has errors
42
+ error_list = self.message.errors()
43
+ if error_list:
44
+ return str(error_list[0].get("msg", "Unknown error"))
45
+ return str(self.message)
46
+
47
+ # def __str__(self):
48
+ # return f"""{repr(self)}
49
+ # Data being validated: {self.data}
50
+ # Pydnantic Model: {self.model}.
51
+ # Reported error: {self.message}."""
34
52
 
35
53
  def to_html_dict(self):
36
54
  return {
@@ -1,6 +1,13 @@
1
1
  import re
2
2
  import textwrap
3
3
 
4
+ # from IPython.core.error import UsageError
5
+
6
+
7
+ class AgentListError(Exception):
8
+ def __init__(self, message):
9
+ super().__init__(message)
10
+
4
11
 
5
12
  class ScenarioError(Exception):
6
13
  documentation = "https://docs.expectedparrot.com/en/latest/scenarios.html#module-edsl.scenarios.Scenario"