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.
- edsl/Base.py +169 -116
- edsl/__init__.py +14 -6
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +358 -146
- edsl/agents/AgentList.py +211 -73
- edsl/agents/Invigilator.py +88 -36
- edsl/agents/InvigilatorBase.py +59 -70
- edsl/agents/PromptConstructor.py +117 -219
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionOptionProcessor.py +172 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +0 -1
- edsl/agents/prompt_helpers.py +3 -3
- edsl/config.py +22 -2
- edsl/conversation/car_buying.py +2 -1
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +1 -1
- edsl/coop/coop.py +104 -42
- edsl/coop/utils.py +14 -14
- edsl/data/Cache.py +21 -14
- edsl/data/CacheEntry.py +12 -15
- edsl/data/CacheHandler.py +33 -12
- edsl/data/__init__.py +4 -3
- edsl/data_transfer_models.py +2 -1
- edsl/enums.py +20 -0
- edsl/exceptions/__init__.py +50 -50
- edsl/exceptions/agents.py +12 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/questions.py +24 -6
- edsl/exceptions/scenarios.py +7 -0
- edsl/inference_services/AnthropicService.py +0 -3
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +209 -0
- edsl/inference_services/AwsBedrock.py +0 -2
- edsl/inference_services/AzureAI.py +0 -2
- edsl/inference_services/GoogleService.py +2 -11
- edsl/inference_services/InferenceServiceABC.py +18 -85
- edsl/inference_services/InferenceServicesCollection.py +105 -80
- edsl/inference_services/MistralAIService.py +0 -3
- edsl/inference_services/OpenAIService.py +1 -4
- edsl/inference_services/PerplexityService.py +0 -3
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +11 -8
- edsl/inference_services/data_structures.py +62 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +188 -0
- edsl/jobs/Answers.py +1 -14
- edsl/jobs/FetchInvigilator.py +40 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +48 -0
- edsl/jobs/Jobs.py +102 -243
- edsl/jobs/JobsChecks.py +35 -10
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +5 -3
- edsl/jobs/JobsRemoteInferenceHandler.py +128 -80
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/buckets/BucketCollection.py +44 -3
- edsl/jobs/buckets/TokenBucket.py +53 -21
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +77 -380
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +4 -49
- edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
- edsl/jobs/tasks/TaskHistory.py +14 -15
- edsl/jobs/tasks/task_status_enum.py +0 -2
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +137 -234
- edsl/language_models/ModelList.py +11 -13
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +0 -1
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/registry.py +49 -59
- edsl/language_models/repair.py +2 -2
- edsl/language_models/utilities.py +5 -4
- edsl/notebooks/Notebook.py +19 -14
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/prompts/Prompt.py +29 -39
- edsl/questions/AnswerValidatorMixin.py +47 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/LoopProcessor.py +149 -0
- edsl/questions/QuestionBase.py +37 -192
- edsl/questions/QuestionBaseGenMixin.py +52 -48
- edsl/questions/QuestionBasePromptsMixin.py +7 -3
- edsl/questions/QuestionCheckBox.py +1 -1
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/QuestionFreeText.py +1 -2
- edsl/questions/QuestionList.py +3 -5
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +66 -22
- edsl/questions/QuestionNumerical.py +1 -3
- edsl/questions/QuestionRank.py +6 -16
- edsl/questions/ResponseValidatorABC.py +37 -11
- edsl/questions/ResponseValidatorFactory.py +28 -0
- edsl/questions/SimpleAskMixin.py +4 -3
- edsl/questions/__init__.py +1 -0
- edsl/questions/derived/QuestionLinearScale.py +6 -3
- edsl/questions/derived/QuestionTopK.py +1 -1
- edsl/questions/descriptors.py +17 -3
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/results/CSSParameterizer.py +1 -1
- edsl/results/Dataset.py +170 -7
- edsl/results/DatasetExportMixin.py +224 -302
- edsl/results/DatasetTree.py +28 -8
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +192 -206
- edsl/results/Results.py +120 -113
- edsl/results/ResultsExportMixin.py +2 -0
- edsl/results/Selector.py +23 -13
- edsl/results/TableDisplay.py +98 -171
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_renderers.py +118 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DirectoryScanner.py +96 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +118 -239
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +90 -193
- edsl/scenarios/ScenarioHtmlMixin.py +4 -3
- edsl/scenarios/ScenarioJoin.py +10 -6
- edsl/scenarios/ScenarioList.py +383 -240
- edsl/scenarios/ScenarioListExportMixin.py +0 -7
- edsl/scenarios/ScenarioListPdfMixin.py +15 -37
- edsl/scenarios/ScenarioSelector.py +156 -0
- edsl/scenarios/__init__.py +1 -2
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/study/ObjectEntry.py +1 -1
- edsl/study/SnapShot.py +1 -1
- edsl/study/Study.py +5 -12
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/Rule.py +5 -4
- edsl/surveys/RuleCollection.py +25 -27
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +199 -771
- edsl/surveys/SurveyCSS.py +20 -8
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/descriptors.py +6 -2
- edsl/surveys/instructions/ChangeInstruction.py +1 -2
- edsl/surveys/instructions/Instruction.py +4 -13
- edsl/surveys/instructions/InstructionCollection.py +11 -6
- edsl/templates/error_reporting/interview_details.html +1 -1
- edsl/templates/error_reporting/report.html +1 -1
- edsl/tools/plotting.py +1 -1
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/utilities.py +35 -23
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/METADATA +12 -10
- edsl-0.1.39.dev2.dist-info/RECORD +352 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev2.dist-info}/LICENSE +0 -0
- {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()
|
edsl/jobs/decorators.py
ADDED
@@ -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
|