edsl 0.1.33__py3-none-any.whl → 0.1.33.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 +3 -9
- edsl/__init__.py +0 -1
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +6 -6
- edsl/agents/Invigilator.py +3 -6
- edsl/agents/InvigilatorBase.py +27 -8
- edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +29 -101
- edsl/config.py +34 -26
- edsl/coop/coop.py +2 -11
- edsl/data_transfer_models.py +73 -26
- edsl/enums.py +0 -2
- edsl/inference_services/GoogleService.py +1 -1
- edsl/inference_services/InferenceServiceABC.py +13 -44
- edsl/inference_services/OpenAIService.py +4 -7
- edsl/inference_services/TestService.py +15 -24
- edsl/inference_services/registry.py +0 -2
- edsl/jobs/Jobs.py +8 -18
- edsl/jobs/buckets/BucketCollection.py +15 -24
- edsl/jobs/buckets/TokenBucket.py +10 -64
- edsl/jobs/interviews/Interview.py +47 -115
- edsl/jobs/interviews/InterviewExceptionEntry.py +0 -2
- edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +0 -16
- edsl/jobs/interviews/retry_management.py +39 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +170 -95
- edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
- edsl/jobs/tasks/TaskHistory.py +0 -17
- edsl/language_models/LanguageModel.py +31 -26
- edsl/language_models/registry.py +9 -13
- edsl/questions/QuestionBase.py +14 -63
- edsl/questions/QuestionBudget.py +41 -93
- edsl/questions/QuestionFreeText.py +0 -6
- edsl/questions/QuestionMultipleChoice.py +23 -8
- edsl/questions/QuestionNumerical.py +4 -5
- edsl/questions/ResponseValidatorABC.py +5 -6
- edsl/questions/derived/QuestionLinearScale.py +1 -4
- edsl/questions/derived/QuestionTopK.py +1 -4
- edsl/questions/derived/QuestionYesNo.py +2 -8
- edsl/results/DatasetExportMixin.py +1 -5
- edsl/results/Result.py +1 -1
- edsl/results/Results.py +1 -4
- edsl/scenarios/FileStore.py +10 -71
- edsl/scenarios/Scenario.py +21 -86
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +0 -13
- edsl/scenarios/ScenarioListPdfMixin.py +4 -150
- edsl/study/Study.py +0 -32
- edsl/surveys/Rule.py +1 -10
- edsl/surveys/RuleCollection.py +3 -19
- edsl/surveys/Survey.py +0 -7
- edsl/templates/error_reporting/interview_details.html +1 -6
- edsl/utilities/utilities.py +1 -9
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +1 -2
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/RECORD +55 -61
- edsl/inference_services/TogetherAIService.py +0 -170
- edsl/jobs/runners/JobsRunnerStatus.py +0 -331
- edsl/questions/Quick.py +0 -41
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +0 -7
- edsl/questions/templates/budget/question_presentation.jinja +0 -7
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -64,7 +64,7 @@ class GoogleService(InferenceServiceABC):
|
|
64
64
|
"stopSequences": self.stopSequences,
|
65
65
|
},
|
66
66
|
}
|
67
|
-
|
67
|
+
print(combined_prompt)
|
68
68
|
async with aiohttp.ClientSession() as session:
|
69
69
|
async with session.post(
|
70
70
|
url, headers=headers, data=json.dumps(data)
|
@@ -1,27 +1,14 @@
|
|
1
1
|
from abc import abstractmethod, ABC
|
2
|
-
import
|
2
|
+
from typing import Any
|
3
3
|
import re
|
4
4
|
from edsl.config import CONFIG
|
5
5
|
|
6
6
|
|
7
7
|
class InferenceServiceABC(ABC):
|
8
|
-
"""
|
9
|
-
Abstract class for inference services.
|
10
|
-
Anthropic: https://docs.anthropic.com/en/api/rate-limits
|
11
|
-
"""
|
12
|
-
|
13
|
-
default_levels = {
|
14
|
-
"google": {"tpm": 2_000_000, "rpm": 15},
|
15
|
-
"openai": {"tpm": 2_000_000, "rpm": 10_000},
|
16
|
-
"anthropic": {"tpm": 2_000_000, "rpm": 500},
|
17
|
-
}
|
8
|
+
"""Abstract class for inference services."""
|
18
9
|
|
10
|
+
# check if child class has cls attribute "key_sequence"
|
19
11
|
def __init_subclass__(cls):
|
20
|
-
"""
|
21
|
-
Check that the subclass has the required attributes.
|
22
|
-
- `key_sequence` attribute determines...
|
23
|
-
- `model_exclude_list` attribute determines...
|
24
|
-
"""
|
25
12
|
if not hasattr(cls, "key_sequence"):
|
26
13
|
raise NotImplementedError(
|
27
14
|
f"Class {cls.__name__} must have a 'key_sequence' attribute."
|
@@ -31,47 +18,29 @@ class InferenceServiceABC(ABC):
|
|
31
18
|
f"Class {cls.__name__} must have a 'model_exclude_list' attribute."
|
32
19
|
)
|
33
20
|
|
34
|
-
|
35
|
-
|
36
|
-
key
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if cls._inference_service_ in cls.default_levels:
|
41
|
-
return int(cls.default_levels[cls._inference_service_][limit_type])
|
42
|
-
|
43
|
-
return int(CONFIG.get(f"EDSL_SERVICE_{limit_type.upper()}_BASELINE"))
|
44
|
-
|
45
|
-
def get_tpm(cls) -> int:
|
46
|
-
"""
|
47
|
-
Returns the TPM for the service. If the service is not defined in the environment variables, it will return the baseline TPM.
|
48
|
-
"""
|
49
|
-
return cls._get_limt(limit_type="tpm")
|
21
|
+
def get_tpm(cls):
|
22
|
+
key = f"EDSL_SERVICE_TPM_{cls._inference_service_.upper()}"
|
23
|
+
if key not in CONFIG:
|
24
|
+
key = "EDSL_SERVICE_TPM_BASELINE"
|
25
|
+
return int(CONFIG.get(key))
|
50
26
|
|
51
27
|
def get_rpm(cls):
|
52
|
-
""
|
53
|
-
|
54
|
-
|
55
|
-
return
|
28
|
+
key = f"EDSL_SERVICE_RPM_{cls._inference_service_.upper()}"
|
29
|
+
if key not in CONFIG:
|
30
|
+
key = "EDSL_SERVICE_RPM_BASELINE"
|
31
|
+
return int(CONFIG.get(key))
|
56
32
|
|
57
33
|
@abstractmethod
|
58
34
|
def available() -> list[str]:
|
59
|
-
"""
|
60
|
-
Returns a list of available models for the service.
|
61
|
-
"""
|
62
35
|
pass
|
63
36
|
|
64
37
|
@abstractmethod
|
65
38
|
def create_model():
|
66
|
-
"""
|
67
|
-
Returns a LanguageModel object.
|
68
|
-
"""
|
69
39
|
pass
|
70
40
|
|
71
41
|
@staticmethod
|
72
42
|
def to_class_name(s):
|
73
|
-
"""
|
74
|
-
Converts a string to a valid class name.
|
43
|
+
"""Convert a string to a valid class name.
|
75
44
|
|
76
45
|
>>> InferenceServiceABC.to_class_name("hello world")
|
77
46
|
'HelloWorld'
|
@@ -187,15 +187,12 @@ class OpenAIService(InferenceServiceABC):
|
|
187
187
|
else:
|
188
188
|
content = user_prompt
|
189
189
|
client = self.async_client()
|
190
|
-
messages = [
|
191
|
-
{"role": "system", "content": system_prompt},
|
192
|
-
{"role": "user", "content": content},
|
193
|
-
]
|
194
|
-
if system_prompt == "" and self.omit_system_prompt_if_empty:
|
195
|
-
messages = messages[1:]
|
196
190
|
params = {
|
197
191
|
"model": self.model,
|
198
|
-
"messages":
|
192
|
+
"messages": [
|
193
|
+
{"role": "system", "content": system_prompt},
|
194
|
+
{"role": "user", "content": content},
|
195
|
+
],
|
199
196
|
"temperature": self.temperature,
|
200
197
|
"max_tokens": self.max_tokens,
|
201
198
|
"top_p": self.top_p,
|
@@ -7,25 +7,14 @@ from edsl.inference_services.rate_limits_cache import rate_limits
|
|
7
7
|
from edsl.utilities.utilities import fix_partial_correct_response
|
8
8
|
|
9
9
|
from edsl.enums import InferenceServiceType
|
10
|
-
import random
|
11
10
|
|
12
11
|
|
13
12
|
class TestService(InferenceServiceABC):
|
14
13
|
"""OpenAI service class."""
|
15
14
|
|
16
|
-
_inference_service_ = "test"
|
17
|
-
_env_key_name_ = None
|
18
|
-
_base_url_ = None
|
19
|
-
|
20
|
-
_sync_client_ = None
|
21
|
-
_async_client_ = None
|
22
|
-
|
23
|
-
_sync_client_instance = None
|
24
|
-
_async_client_instance = None
|
25
|
-
|
26
15
|
key_sequence = None
|
27
|
-
usage_sequence = None
|
28
16
|
model_exclude_list = []
|
17
|
+
_inference_service_ = "test"
|
29
18
|
input_token_name = "prompt_tokens"
|
30
19
|
output_token_name = "completion_tokens"
|
31
20
|
|
@@ -56,25 +45,27 @@ class TestService(InferenceServiceABC):
|
|
56
45
|
return "Hello, world"
|
57
46
|
|
58
47
|
async def async_execute_model_call(
|
59
|
-
self,
|
60
|
-
user_prompt: str,
|
61
|
-
system_prompt: str,
|
62
|
-
encoded_image=None,
|
48
|
+
self, user_prompt: str, system_prompt: str
|
63
49
|
) -> dict[str, Any]:
|
64
50
|
await asyncio.sleep(0.1)
|
65
51
|
# return {"message": """{"answer": "Hello, world"}"""}
|
66
|
-
|
67
52
|
if hasattr(self, "throw_exception") and self.throw_exception:
|
68
|
-
|
69
|
-
p = self.exception_probability
|
70
|
-
else:
|
71
|
-
p = 1
|
72
|
-
|
73
|
-
if random.random() < p:
|
74
|
-
raise Exception("This is a test error")
|
53
|
+
raise Exception("This is a test error")
|
75
54
|
return {
|
76
55
|
"message": [{"text": f"{self._canned_response}"}],
|
77
56
|
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
|
78
57
|
}
|
79
58
|
|
80
59
|
return TestServiceLanguageModel
|
60
|
+
|
61
|
+
# _inference_service_ = "openai"
|
62
|
+
# _env_key_name_ = "OPENAI_API_KEY"
|
63
|
+
# _base_url_ = None
|
64
|
+
|
65
|
+
# _sync_client_ = openai.OpenAI
|
66
|
+
# _async_client_ = openai.AsyncOpenAI
|
67
|
+
|
68
|
+
# _sync_client_instance = None
|
69
|
+
# _async_client_instance = None
|
70
|
+
|
71
|
+
# key_sequence = ["choices", 0, "message", "content"]
|
@@ -12,7 +12,6 @@ from edsl.inference_services.AzureAI import AzureAIService
|
|
12
12
|
from edsl.inference_services.OllamaService import OllamaService
|
13
13
|
from edsl.inference_services.TestService import TestService
|
14
14
|
from edsl.inference_services.MistralAIService import MistralAIService
|
15
|
-
from edsl.inference_services.TogetherAIService import TogetherAIService
|
16
15
|
|
17
16
|
default = InferenceServicesCollection(
|
18
17
|
[
|
@@ -26,6 +25,5 @@ default = InferenceServicesCollection(
|
|
26
25
|
OllamaService,
|
27
26
|
TestService,
|
28
27
|
MistralAIService,
|
29
|
-
TogetherAIService,
|
30
28
|
]
|
31
29
|
)
|
edsl/jobs/Jobs.py
CHANGED
@@ -460,12 +460,6 @@ class Jobs(Base):
|
|
460
460
|
if warn:
|
461
461
|
warnings.warn(message)
|
462
462
|
|
463
|
-
if self.scenarios.has_jinja_braces:
|
464
|
-
warnings.warn(
|
465
|
-
"The scenarios have Jinja braces ({{ and }}). Converting to '<<' and '>>'. If you want a different conversion, use the convert_jinja_braces method first to modify the scenario."
|
466
|
-
)
|
467
|
-
self.scenarios = self.scenarios.convert_jinja_braces()
|
468
|
-
|
469
463
|
@property
|
470
464
|
def skip_retry(self):
|
471
465
|
if not hasattr(self, "_skip_retry"):
|
@@ -492,7 +486,6 @@ class Jobs(Base):
|
|
492
486
|
remote_inference_description: Optional[str] = None,
|
493
487
|
skip_retry: bool = False,
|
494
488
|
raise_validation_errors: bool = False,
|
495
|
-
disable_remote_inference: bool = False,
|
496
489
|
) -> Results:
|
497
490
|
"""
|
498
491
|
Runs the Job: conducts Interviews and returns their results.
|
@@ -515,17 +508,14 @@ class Jobs(Base):
|
|
515
508
|
|
516
509
|
self.verbose = verbose
|
517
510
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
remote_inference = user_edsl_settings.get("remote_inference", False)
|
527
|
-
except Exception:
|
528
|
-
pass
|
511
|
+
try:
|
512
|
+
coop = Coop()
|
513
|
+
user_edsl_settings = coop.edsl_settings
|
514
|
+
remote_cache = user_edsl_settings["remote_caching"]
|
515
|
+
remote_inference = user_edsl_settings["remote_inference"]
|
516
|
+
except Exception:
|
517
|
+
remote_cache = False
|
518
|
+
remote_inference = False
|
529
519
|
|
530
520
|
if remote_inference:
|
531
521
|
import time
|
@@ -13,8 +13,6 @@ class BucketCollection(UserDict):
|
|
13
13
|
def __init__(self, infinity_buckets=False):
|
14
14
|
super().__init__()
|
15
15
|
self.infinity_buckets = infinity_buckets
|
16
|
-
self.models_to_services = {}
|
17
|
-
self.services_to_buckets = {}
|
18
16
|
|
19
17
|
def __repr__(self):
|
20
18
|
return f"BucketCollection({self.data})"
|
@@ -23,7 +21,6 @@ class BucketCollection(UserDict):
|
|
23
21
|
"""Adds a model to the bucket collection.
|
24
22
|
|
25
23
|
This will create the token and request buckets for the model."""
|
26
|
-
|
27
24
|
# compute the TPS and RPS from the model
|
28
25
|
if not self.infinity_buckets:
|
29
26
|
TPS = model.TPM / 60.0
|
@@ -32,28 +29,22 @@ class BucketCollection(UserDict):
|
|
32
29
|
TPS = float("inf")
|
33
30
|
RPS = float("inf")
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
)
|
50
|
-
self.services_to_buckets[service] = ModelBuckets(
|
51
|
-
requests_bucket, tokens_bucket
|
52
|
-
)
|
53
|
-
self.models_to_services[model.model] = service
|
54
|
-
self[model] = self.services_to_buckets[service]
|
32
|
+
# create the buckets
|
33
|
+
requests_bucket = TokenBucket(
|
34
|
+
bucket_name=model.model,
|
35
|
+
bucket_type="requests",
|
36
|
+
capacity=RPS,
|
37
|
+
refill_rate=RPS,
|
38
|
+
)
|
39
|
+
tokens_bucket = TokenBucket(
|
40
|
+
bucket_name=model.model, bucket_type="tokens", capacity=TPS, refill_rate=TPS
|
41
|
+
)
|
42
|
+
model_buckets = ModelBuckets(requests_bucket, tokens_bucket)
|
43
|
+
if model in self:
|
44
|
+
# it if already exists, combine the buckets
|
45
|
+
self[model] += model_buckets
|
55
46
|
else:
|
56
|
-
self[model] =
|
47
|
+
self[model] = model_buckets
|
57
48
|
|
58
49
|
def visualize(self) -> dict:
|
59
50
|
"""Visualize the token and request buckets for each model."""
|
edsl/jobs/buckets/TokenBucket.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Union, List, Any
|
1
|
+
from typing import Union, List, Any
|
2
2
|
import asyncio
|
3
3
|
import time
|
4
4
|
|
@@ -17,12 +17,6 @@ class TokenBucket:
|
|
17
17
|
self.bucket_name = bucket_name
|
18
18
|
self.bucket_type = bucket_type
|
19
19
|
self.capacity = capacity # Maximum number of tokens
|
20
|
-
self.added_tokens = 0
|
21
|
-
|
22
|
-
self.target_rate = (
|
23
|
-
capacity * 60
|
24
|
-
) # set this here because it can change with turbo mode
|
25
|
-
|
26
20
|
self._old_capacity = capacity
|
27
21
|
self.tokens = capacity # Current number of available tokens
|
28
22
|
self.refill_rate = refill_rate # Rate at which tokens are refilled
|
@@ -31,12 +25,6 @@ class TokenBucket:
|
|
31
25
|
self.log: List[Any] = []
|
32
26
|
self.turbo_mode = False
|
33
27
|
|
34
|
-
self.creation_time = time.monotonic()
|
35
|
-
|
36
|
-
self.num_requests = 0
|
37
|
-
self.num_released = 0
|
38
|
-
self.tokens_returned = 0
|
39
|
-
|
40
28
|
def turbo_mode_on(self):
|
41
29
|
"""Set the refill rate to infinity."""
|
42
30
|
if self.turbo_mode:
|
@@ -81,7 +69,6 @@ class TokenBucket:
|
|
81
69
|
>>> bucket.tokens
|
82
70
|
10
|
83
71
|
"""
|
84
|
-
self.tokens_returned += tokens
|
85
72
|
self.tokens = min(self.capacity, self.tokens + tokens)
|
86
73
|
self.log.append((time.monotonic(), self.tokens))
|
87
74
|
|
@@ -146,12 +133,15 @@ class TokenBucket:
|
|
146
133
|
>>> bucket.capacity
|
147
134
|
12.100000000000001
|
148
135
|
"""
|
149
|
-
self.num_requests += amount
|
150
136
|
if amount >= self.capacity:
|
151
137
|
if not cheat_bucket_capacity:
|
152
138
|
msg = f"Requested amount exceeds bucket capacity. Bucket capacity: {self.capacity}, requested amount: {amount}. As the bucket never overflows, the requested amount will never be available."
|
153
139
|
raise ValueError(msg)
|
154
140
|
else:
|
141
|
+
# self.tokens = 0 # clear the bucket but let it go through
|
142
|
+
# print(
|
143
|
+
# f"""The requested amount, {amount}, exceeds the current bucket capacity of {self.capacity}.Increasing bucket capacity to {amount} * 1.10 accommodate the requested amount."""
|
144
|
+
# )
|
155
145
|
self.capacity = amount * 1.10
|
156
146
|
self._old_capacity = self.capacity
|
157
147
|
|
@@ -163,10 +153,14 @@ class TokenBucket:
|
|
163
153
|
break
|
164
154
|
|
165
155
|
wait_time = self.wait_time(amount)
|
156
|
+
# print(f"Waiting for {wait_time:.4f} seconds")
|
166
157
|
if wait_time > 0:
|
158
|
+
# print(f"Waiting for {wait_time:.4f} seconds")
|
167
159
|
await asyncio.sleep(wait_time)
|
168
160
|
|
169
|
-
|
161
|
+
# total_elapsed = time.monotonic() - start_time
|
162
|
+
# print(f"Total time to acquire tokens: {total_elapsed:.4f} seconds")
|
163
|
+
|
170
164
|
now = time.monotonic()
|
171
165
|
self.log.append((now, self.tokens))
|
172
166
|
return None
|
@@ -193,54 +187,6 @@ class TokenBucket:
|
|
193
187
|
plt.tight_layout()
|
194
188
|
plt.show()
|
195
189
|
|
196
|
-
def get_throughput(self, time_window: Optional[float] = None) -> float:
|
197
|
-
"""
|
198
|
-
Calculate the empirical bucket throughput in tokens per minute for the specified time window.
|
199
|
-
|
200
|
-
:param time_window: The time window in seconds to calculate the throughput for.
|
201
|
-
:return: The throughput in tokens per minute.
|
202
|
-
|
203
|
-
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=100, refill_rate=10)
|
204
|
-
>>> asyncio.run(bucket.get_tokens(50))
|
205
|
-
>>> time.sleep(1) # Wait for 1 second
|
206
|
-
>>> asyncio.run(bucket.get_tokens(30))
|
207
|
-
>>> throughput = bucket.get_throughput(1)
|
208
|
-
>>> 4750 < throughput < 4850
|
209
|
-
True
|
210
|
-
"""
|
211
|
-
now = time.monotonic()
|
212
|
-
|
213
|
-
if time_window is None:
|
214
|
-
start_time = self.creation_time
|
215
|
-
else:
|
216
|
-
start_time = now - time_window
|
217
|
-
|
218
|
-
if start_time < self.creation_time:
|
219
|
-
start_time = self.creation_time
|
220
|
-
|
221
|
-
elapsed_time = now - start_time
|
222
|
-
|
223
|
-
return (self.num_released / elapsed_time) * 60
|
224
|
-
|
225
|
-
# # Filter log entries within the time window
|
226
|
-
# relevant_log = [(t, tokens) for t, tokens in self.log if t >= start_time]
|
227
|
-
|
228
|
-
# if len(relevant_log) < 2:
|
229
|
-
# return 0 # Not enough data points to calculate throughput
|
230
|
-
|
231
|
-
# # Calculate total tokens used
|
232
|
-
# initial_tokens = relevant_log[0][1]
|
233
|
-
# final_tokens = relevant_log[-1][1]
|
234
|
-
# tokens_used = self.num_released - (final_tokens - initial_tokens)
|
235
|
-
|
236
|
-
# # Calculate actual time elapsed
|
237
|
-
# actual_time_elapsed = relevant_log[-1][0] - relevant_log[0][0]
|
238
|
-
|
239
|
-
# # Calculate throughput in tokens per minute
|
240
|
-
# throughput = (tokens_used / actual_time_elapsed) * 60
|
241
|
-
|
242
|
-
# return throughput
|
243
|
-
|
244
190
|
|
245
191
|
if __name__ == "__main__":
|
246
192
|
import doctest
|