edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev2__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 (194) hide show
  1. edsl/Base.py +169 -116
  2. edsl/__init__.py +14 -6
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +358 -146
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +88 -36
  7. edsl/agents/InvigilatorBase.py +59 -70
  8. edsl/agents/PromptConstructor.py +117 -219
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionOptionProcessor.py +172 -0
  11. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  12. edsl/agents/__init__.py +0 -1
  13. edsl/agents/prompt_helpers.py +3 -3
  14. edsl/config.py +22 -2
  15. edsl/conversation/car_buying.py +2 -1
  16. edsl/coop/CoopFunctionsMixin.py +15 -0
  17. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  18. edsl/coop/PriceFetcher.py +1 -1
  19. edsl/coop/coop.py +104 -42
  20. edsl/coop/utils.py +14 -14
  21. edsl/data/Cache.py +21 -14
  22. edsl/data/CacheEntry.py +12 -15
  23. edsl/data/CacheHandler.py +33 -12
  24. edsl/data/__init__.py +4 -3
  25. edsl/data_transfer_models.py +2 -1
  26. edsl/enums.py +20 -0
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +12 -0
  29. edsl/exceptions/inference_services.py +5 -0
  30. edsl/exceptions/questions.py +24 -6
  31. edsl/exceptions/scenarios.py +7 -0
  32. edsl/inference_services/AnthropicService.py +0 -3
  33. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  34. edsl/inference_services/AvailableModelFetcher.py +209 -0
  35. edsl/inference_services/AwsBedrock.py +0 -2
  36. edsl/inference_services/AzureAI.py +0 -2
  37. edsl/inference_services/GoogleService.py +2 -11
  38. edsl/inference_services/InferenceServiceABC.py +18 -85
  39. edsl/inference_services/InferenceServicesCollection.py +105 -80
  40. edsl/inference_services/MistralAIService.py +0 -3
  41. edsl/inference_services/OpenAIService.py +1 -4
  42. edsl/inference_services/PerplexityService.py +0 -3
  43. edsl/inference_services/ServiceAvailability.py +135 -0
  44. edsl/inference_services/TestService.py +11 -8
  45. edsl/inference_services/data_structures.py +62 -0
  46. edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
  47. edsl/jobs/Answers.py +1 -14
  48. edsl/jobs/FetchInvigilator.py +40 -0
  49. edsl/jobs/InterviewTaskManager.py +98 -0
  50. edsl/jobs/InterviewsConstructor.py +48 -0
  51. edsl/jobs/Jobs.py +102 -243
  52. edsl/jobs/JobsChecks.py +35 -10
  53. edsl/jobs/JobsComponentConstructor.py +189 -0
  54. edsl/jobs/JobsPrompts.py +5 -3
  55. edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
  56. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  57. edsl/jobs/RequestTokenEstimator.py +30 -0
  58. edsl/jobs/buckets/BucketCollection.py +44 -3
  59. edsl/jobs/buckets/TokenBucket.py +53 -21
  60. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  61. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  62. edsl/jobs/decorators.py +35 -0
  63. edsl/jobs/interviews/Interview.py +77 -380
  64. edsl/jobs/jobs_status_enums.py +9 -0
  65. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  66. edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
  67. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  68. edsl/jobs/tasks/TaskHistory.py +14 -15
  69. edsl/jobs/tasks/task_status_enum.py +0 -2
  70. edsl/language_models/ComputeCost.py +63 -0
  71. edsl/language_models/LanguageModel.py +137 -234
  72. edsl/language_models/ModelList.py +11 -13
  73. edsl/language_models/PriceManager.py +127 -0
  74. edsl/language_models/RawResponseHandler.py +106 -0
  75. edsl/language_models/ServiceDataSources.py +0 -0
  76. edsl/language_models/__init__.py +0 -1
  77. edsl/language_models/key_management/KeyLookup.py +63 -0
  78. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  79. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  80. edsl/language_models/key_management/__init__.py +0 -0
  81. edsl/language_models/key_management/models.py +131 -0
  82. edsl/language_models/registry.py +49 -59
  83. edsl/language_models/repair.py +2 -2
  84. edsl/language_models/utilities.py +5 -4
  85. edsl/notebooks/Notebook.py +19 -14
  86. edsl/notebooks/NotebookToLaTeX.py +142 -0
  87. edsl/prompts/Prompt.py +29 -39
  88. edsl/questions/AnswerValidatorMixin.py +47 -2
  89. edsl/questions/ExceptionExplainer.py +77 -0
  90. edsl/questions/HTMLQuestion.py +103 -0
  91. edsl/questions/LoopProcessor.py +149 -0
  92. edsl/questions/QuestionBase.py +37 -192
  93. edsl/questions/QuestionBaseGenMixin.py +52 -48
  94. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  95. edsl/questions/QuestionCheckBox.py +1 -1
  96. edsl/questions/QuestionExtract.py +1 -1
  97. edsl/questions/QuestionFreeText.py +1 -2
  98. edsl/questions/QuestionList.py +3 -5
  99. edsl/questions/QuestionMatrix.py +265 -0
  100. edsl/questions/QuestionMultipleChoice.py +66 -22
  101. edsl/questions/QuestionNumerical.py +1 -3
  102. edsl/questions/QuestionRank.py +6 -16
  103. edsl/questions/ResponseValidatorABC.py +37 -11
  104. edsl/questions/ResponseValidatorFactory.py +28 -0
  105. edsl/questions/SimpleAskMixin.py +4 -3
  106. edsl/questions/__init__.py +1 -0
  107. edsl/questions/derived/QuestionLinearScale.py +6 -3
  108. edsl/questions/derived/QuestionTopK.py +1 -1
  109. edsl/questions/descriptors.py +17 -3
  110. edsl/questions/question_registry.py +1 -1
  111. edsl/questions/templates/matrix/__init__.py +1 -0
  112. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  113. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  114. edsl/results/CSSParameterizer.py +1 -1
  115. edsl/results/Dataset.py +170 -7
  116. edsl/results/DatasetExportMixin.py +224 -302
  117. edsl/results/DatasetTree.py +28 -8
  118. edsl/results/MarkdownToDocx.py +122 -0
  119. edsl/results/MarkdownToPDF.py +111 -0
  120. edsl/results/Result.py +192 -206
  121. edsl/results/Results.py +120 -113
  122. edsl/results/ResultsExportMixin.py +2 -0
  123. edsl/results/Selector.py +23 -13
  124. edsl/results/TableDisplay.py +98 -171
  125. edsl/results/TextEditor.py +50 -0
  126. edsl/results/__init__.py +1 -1
  127. edsl/results/smart_objects.py +96 -0
  128. edsl/results/table_data_class.py +12 -0
  129. edsl/results/table_renderers.py +118 -0
  130. edsl/scenarios/ConstructDownloadLink.py +109 -0
  131. edsl/scenarios/DirectoryScanner.py +96 -0
  132. edsl/scenarios/DocumentChunker.py +102 -0
  133. edsl/scenarios/DocxScenario.py +16 -0
  134. edsl/scenarios/FileStore.py +118 -239
  135. edsl/scenarios/PdfExtractor.py +40 -0
  136. edsl/scenarios/Scenario.py +90 -193
  137. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  138. edsl/scenarios/ScenarioJoin.py +10 -6
  139. edsl/scenarios/ScenarioList.py +383 -240
  140. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  141. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  142. edsl/scenarios/ScenarioSelector.py +156 -0
  143. edsl/scenarios/__init__.py +1 -2
  144. edsl/scenarios/file_methods.py +85 -0
  145. edsl/scenarios/handlers/__init__.py +13 -0
  146. edsl/scenarios/handlers/csv.py +38 -0
  147. edsl/scenarios/handlers/docx.py +76 -0
  148. edsl/scenarios/handlers/html.py +37 -0
  149. edsl/scenarios/handlers/json.py +111 -0
  150. edsl/scenarios/handlers/latex.py +5 -0
  151. edsl/scenarios/handlers/md.py +51 -0
  152. edsl/scenarios/handlers/pdf.py +68 -0
  153. edsl/scenarios/handlers/png.py +39 -0
  154. edsl/scenarios/handlers/pptx.py +105 -0
  155. edsl/scenarios/handlers/py.py +294 -0
  156. edsl/scenarios/handlers/sql.py +313 -0
  157. edsl/scenarios/handlers/sqlite.py +149 -0
  158. edsl/scenarios/handlers/txt.py +33 -0
  159. edsl/study/ObjectEntry.py +1 -1
  160. edsl/study/SnapShot.py +1 -1
  161. edsl/study/Study.py +5 -12
  162. edsl/surveys/ConstructDAG.py +92 -0
  163. edsl/surveys/EditSurvey.py +221 -0
  164. edsl/surveys/InstructionHandler.py +100 -0
  165. edsl/surveys/MemoryManagement.py +72 -0
  166. edsl/surveys/Rule.py +5 -4
  167. edsl/surveys/RuleCollection.py +25 -27
  168. edsl/surveys/RuleManager.py +172 -0
  169. edsl/surveys/Simulator.py +75 -0
  170. edsl/surveys/Survey.py +199 -771
  171. edsl/surveys/SurveyCSS.py +20 -8
  172. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  173. edsl/surveys/SurveyToApp.py +141 -0
  174. edsl/surveys/__init__.py +4 -2
  175. edsl/surveys/descriptors.py +6 -2
  176. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  177. edsl/surveys/instructions/Instruction.py +4 -13
  178. edsl/surveys/instructions/InstructionCollection.py +11 -6
  179. edsl/templates/error_reporting/interview_details.html +1 -1
  180. edsl/templates/error_reporting/report.html +1 -1
  181. edsl/tools/plotting.py +1 -1
  182. edsl/utilities/PrettyList.py +56 -0
  183. edsl/utilities/is_notebook.py +18 -0
  184. edsl/utilities/is_valid_variable_name.py +11 -0
  185. edsl/utilities/remove_edsl_version.py +24 -0
  186. edsl/utilities/utilities.py +35 -23
  187. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
  188. edsl-0.1.39.dev2.dist-info/RECORD +352 -0
  189. edsl/language_models/KeyLookup.py +0 -30
  190. edsl/language_models/unused/ReplicateBase.py +0 -83
  191. edsl/results/ResultsDBMixin.py +0 -238
  192. edsl-0.1.39.dev1.dist-info/RECORD +0 -277
  193. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
  194. {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,191 @@
1
+ from typing import Union, Optional
2
+ import asyncio
3
+ import time
4
+ import aiohttp
5
+
6
+
7
+ class TokenBucketClient:
8
+ """REST API client version of TokenBucket that maintains the same interface
9
+ by delegating to a server running the original TokenBucket implementation."""
10
+
11
+ def __init__(
12
+ self,
13
+ *,
14
+ bucket_name: str,
15
+ bucket_type: str,
16
+ capacity: Union[int, float],
17
+ refill_rate: Union[int, float],
18
+ api_base_url: str = "http://localhost:8000",
19
+ ):
20
+ self.bucket_name = bucket_name
21
+ self.bucket_type = bucket_type
22
+ self.capacity = capacity
23
+ self.refill_rate = refill_rate
24
+ self.api_base_url = api_base_url
25
+ self.bucket_id = f"{bucket_name}_{bucket_type}"
26
+
27
+ # Initialize the bucket on the server
28
+ asyncio.run(self._create_bucket())
29
+
30
+ # Cache some values locally
31
+ self.creation_time = time.monotonic()
32
+ self.turbo_mode = False
33
+
34
+ async def _create_bucket(self):
35
+ async with aiohttp.ClientSession() as session:
36
+ payload = {
37
+ "bucket_name": self.bucket_name,
38
+ "bucket_type": self.bucket_type,
39
+ "capacity": self.capacity,
40
+ "refill_rate": self.refill_rate,
41
+ }
42
+ async with session.post(
43
+ f"{self.api_base_url}/bucket",
44
+ json=payload,
45
+ ) as response:
46
+ if response.status != 200:
47
+ raise ValueError(f"Unexpected error: {await response.text()}")
48
+
49
+ result = await response.json()
50
+ if result["status"] == "existing":
51
+ # Update our local values to match the existing bucket
52
+ self.capacity = float(result["bucket"]["capacity"])
53
+ self.refill_rate = float(result["bucket"]["refill_rate"])
54
+
55
+ def turbo_mode_on(self):
56
+ """Set the refill rate to infinity."""
57
+ asyncio.run(self._set_turbo_mode(True))
58
+ self.turbo_mode = True
59
+
60
+ def turbo_mode_off(self):
61
+ """Restore the refill rate to its original value."""
62
+ asyncio.run(self._set_turbo_mode(False))
63
+ self.turbo_mode = False
64
+
65
+ async def add_tokens(self, amount: Union[int, float]):
66
+ """Add tokens to the bucket."""
67
+ async with aiohttp.ClientSession() as session:
68
+ async with session.post(
69
+ f"{self.api_base_url}/bucket/{self.bucket_id}/add_tokens",
70
+ params={"amount": amount},
71
+ ) as response:
72
+ if response.status != 200:
73
+ raise ValueError(f"Failed to add tokens: {await response.text()}")
74
+
75
+ async def _set_turbo_mode(self, state: bool):
76
+ async with aiohttp.ClientSession() as session:
77
+ async with session.post(
78
+ f"{self.api_base_url}/bucket/{self.bucket_id}/turbo_mode/{str(state).lower()}"
79
+ ) as response:
80
+ if response.status != 200:
81
+ raise ValueError(
82
+ f"Failed to set turbo mode: {await response.text()}"
83
+ )
84
+
85
+ async def get_tokens(
86
+ self, amount: Union[int, float] = 1, cheat_bucket_capacity=True
87
+ ) -> None:
88
+ async with aiohttp.ClientSession() as session:
89
+ async with session.post(
90
+ f"{self.api_base_url}/bucket/{self.bucket_id}/get_tokens",
91
+ params={
92
+ "amount": amount,
93
+ "cheat_bucket_capacity": int(cheat_bucket_capacity),
94
+ },
95
+ ) as response:
96
+ if response.status != 200:
97
+ raise ValueError(f"Failed to get tokens: {await response.text()}")
98
+
99
+ def get_throughput(self, time_window: Optional[float] = None) -> float:
100
+ status = asyncio.run(self._get_status())
101
+ now = time.monotonic()
102
+
103
+ if time_window is None:
104
+ start_time = self.creation_time
105
+ else:
106
+ start_time = now - time_window
107
+
108
+ if start_time < self.creation_time:
109
+ start_time = self.creation_time
110
+
111
+ elapsed_time = now - start_time
112
+
113
+ if elapsed_time == 0:
114
+ return status["num_released"] / 0.001
115
+
116
+ return (status["num_released"] / elapsed_time) * 60
117
+
118
+ async def _get_status(self) -> dict:
119
+ async with aiohttp.ClientSession() as session:
120
+ async with session.get(
121
+ f"{self.api_base_url}/bucket/{self.bucket_id}/status"
122
+ ) as response:
123
+ if response.status != 200:
124
+ raise ValueError(
125
+ f"Failed to get bucket status: {await response.text()}"
126
+ )
127
+ return await response.json()
128
+
129
+ def __add__(self, other) -> "TokenBucketClient":
130
+ """Combine two token buckets."""
131
+ return TokenBucketClient(
132
+ bucket_name=self.bucket_name,
133
+ bucket_type=self.bucket_type,
134
+ capacity=min(self.capacity, other.capacity),
135
+ refill_rate=min(self.refill_rate, other.refill_rate),
136
+ api_base_url=self.api_base_url,
137
+ )
138
+
139
+ @property
140
+ def tokens(self) -> float:
141
+ """Get the number of tokens remaining in the bucket."""
142
+ status = asyncio.run(self._get_status())
143
+ return float(status["tokens"])
144
+
145
+ def wait_time(self, requested_tokens: Union[float, int]) -> float:
146
+ """Calculate the time to wait for the requested number of tokens."""
147
+ # self.refill() # Update the current token count
148
+ if self.tokens >= float(requested_tokens):
149
+ return 0.0
150
+ try:
151
+ return (requested_tokens - self.tokens) / self.refill_rate
152
+ except Exception as e:
153
+ raise ValueError(f"Error calculating wait time: {e}")
154
+
155
+ # def wait_time(self, num_tokens: Union[int, float]) -> float:
156
+ # return 0 # TODO - Need to implement this on the server side
157
+
158
+ def visualize(self):
159
+ """Visualize the token bucket over time."""
160
+ status = asyncio.run(self._get_status())
161
+ times, tokens = zip(*status["log"])
162
+ start_time = times[0]
163
+ times = [t - start_time for t in times]
164
+
165
+ from matplotlib import pyplot as plt
166
+
167
+ plt.figure(figsize=(10, 6))
168
+ plt.plot(times, tokens, label="Tokens Available")
169
+ plt.xlabel("Time (seconds)", fontsize=12)
170
+ plt.ylabel("Number of Tokens", fontsize=12)
171
+ details = f"{self.bucket_name} ({self.bucket_type}) Bucket Usage Over Time\nCapacity: {self.capacity:.1f}, Refill Rate: {self.refill_rate:.1f}/second"
172
+ plt.title(details, fontsize=14)
173
+ plt.legend()
174
+ plt.grid(True)
175
+ plt.tight_layout()
176
+ plt.show()
177
+
178
+
179
+ if __name__ == "__main__":
180
+ import doctest
181
+
182
+ doctest.testmod()
183
+ # bucket = TokenBucketClient(
184
+ # bucket_name="test", bucket_type="test", capacity=100, refill_rate=10
185
+ # )
186
+ # asyncio.run(bucket.get_tokens(50))
187
+ # time.sleep(1) # Wait for 1 second
188
+ # asyncio.run(bucket.get_tokens(30))
189
+ # throughput = bucket.get_throughput(1)
190
+ # print(throughput)
191
+ # bucket.visualize()
@@ -0,0 +1,35 @@
1
+ from functools import wraps
2
+ from threading import RLock
3
+ import inspect
4
+
5
+
6
+ def synchronized_class(wrapped_class):
7
+ """Class decorator that makes all methods thread-safe."""
8
+
9
+ # Add a lock to the class
10
+ setattr(wrapped_class, "_lock", RLock())
11
+
12
+ # Get all methods from the class
13
+ for name, method in inspect.getmembers(wrapped_class, inspect.isfunction):
14
+ # Skip magic methods except __getitem__, __setitem__, __delitem__
15
+ if name.startswith("__") and name not in [
16
+ "__getitem__",
17
+ "__setitem__",
18
+ "__delitem__",
19
+ ]:
20
+ continue
21
+
22
+ # Create synchronized version of the method
23
+ def create_synchronized_method(method):
24
+ @wraps(method)
25
+ def synchronized_method(*args, **kwargs):
26
+ instance = args[0] # first arg is self
27
+ with instance._lock:
28
+ return method(*args, **kwargs)
29
+
30
+ return synchronized_method
31
+
32
+ # Replace the original method with synchronized version
33
+ setattr(wrapped_class, name, create_synchronized_method(method))
34
+
35
+ return wrapped_class